File: MS\Internal\Automation\ClientEventManager.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\UIAutomation\UIAutomationClient\UIAutomationClient.csproj (UIAutomationClient)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// Description: Manages the listeners for one accessibility aid or test application.
 
using System.Windows;
using System.Collections;
using System.Diagnostics;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using MS.Win32;
 
using System;
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
namespace MS.Internal.Automation
{
     // Manages the listeners for one accessibility aid or test application.  Locking
     // is used in all public methods to allow for multiple threads in a client process.
    internal static class ClientEventManager
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Static class, no ctor
 
        #endregion Constructors
 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // AddFocusListener - Adds a focus listener for this client.
        internal static void AddFocusListener(Delegate eventCallback, EventListener l)
        {
            AddRootListener(Tracker.Focus, eventCallback, l);
        }
 
        // RemoveFocusListener - Removes a focus change listener from this client
        // and notifies the UIAccess server
        internal static void RemoveFocusListener(Delegate eventCallback)
        {
            RemoveRootListener(AutomationElement.AutomationFocusChangedEvent, Tracker.Focus, eventCallback);
        }
 
        // AddListener - Adds a listener for this client and notifies the UIA server.  All event
        // handler additions call through to this method.
        internal static void AddListener(AutomationElement rawEl, Delegate eventCallback, EventListener l)
        {
            lock (_classLock)
            {
                // If we are adding a listener then a proxy could be created as a result of an event so make sure they are loaded
                ProxyManager.LoadDefaultProxies();
 
                if (_listeners == null)
                {
                    // enough space for 16 AddXxxListeners (100 bytes)
                    _listeners = new ArrayList(16);
                }
 
                // Start the callback queue that gets us off the server's
                // UI thread when events arrive cross-proc
                CheckStartCallbackQueueing();
 
                //
                // The framework handles some events on behalf of providers; do those here
                //
 
                // If listening for BoundingRectangleProperty then may need to start listening on the
                // client-side for LocationChange WinEvent (only use *one* BoundingRectTracker instance).
                if (_winEventTrackers[(int)Tracker.BoundingRect] == null && HasProperty(AutomationElement.BoundingRectangleProperty, l.Properties))
                {
                    // There may be special cases to not map BoundingRect to WinEvent. One may be
                    // where rawEl is a non-top-level native implementation and TreeScope == Element.
                    // Another may be if rawEl is a native impl and TreeScope includes descendents then
                    // may not need to worry about BoundingRect prop change on hosted Win32 UI? I think the
                    // answer is 'yes' because BoundingRectangleProperty should fire events for the top-most
                    // element that moved in the hierarchy.  So if hosted Win32 content Rect changes w/o
                    // the host changing then it would fire this event.
                    // Note: Part of cleaning up WinEvent listeners is to move away from WinEvent handlers calling UIA client handlers
                    AddWinEventListener(Tracker.BoundingRect, new BoundingRectTracker());
                }
 
                // Start listening for menu event in order to raise MenuOpened/Closed events.
                if ( _winEventTrackers [(int)Tracker.MenuOpenedOrClosed] == null && (l.EventId == AutomationElement.MenuOpenedEvent || l.EventId == AutomationElement.MenuClosedEvent) )
                {
                    AddWinEventListener( Tracker.MenuOpenedOrClosed, new MenuTracker( new MenuHandler( OnMenuEvent ) ) );
                }
 
                // Begin watching for hwnd open/close/show/hide so can advise of what events are being listened for.
                // Only advise UI contexts of events being added if the event might be raised by a provider.
                // TopLevelWindow event is raised by UI Automation framework so no need to track new UI.
                // Are there other events like this where Advise can be skipped?
                if (_winEventTrackers[(int)Tracker.WindowShowOrOpen] == null )
                {
                    AddWinEventListener( Tracker.WindowShowOrOpen, new WindowShowOrOpenTracker( new WindowShowOrOpenHandler( OnWindowShowOrOpen ) ) );
                    AddWinEventListener( Tracker.WindowHideOrClose, new WindowHideOrCloseTracker( new WindowHideOrCloseHandler( OnWindowHideOrClose ) ) );
                }
 
                // If listening for WindowInteractionStateProperty then may need to start listening on the
                // client-side for ObjectStateChange WinEvent.
                if (_winEventTrackers[(int)Tracker.WindowInteractionState] == null && HasProperty(WindowPattern.WindowInteractionStateProperty, l.Properties))
                {
                    AddWinEventListener(Tracker.WindowInteractionState, new WindowInteractionStateTracker());
                }
 
                // If listening for WindowVisualStateProperty then may need to start listening on the
                // client-side for ObjectLocationChange WinEvent.
                if (_winEventTrackers[(int)Tracker.WindowVisualState] == null && HasProperty(WindowPattern.WindowVisualStateProperty, l.Properties))
                {
                    AddWinEventListener(Tracker.WindowVisualState, new WindowVisualStateTracker());
                }
 
                // Wrap and store this record on the client...
                EventListenerClientSide ec = new EventListenerClientSide(rawEl, eventCallback, l);
                _listeners.Add(ec);
 
                // Only advise UI contexts of events being added if the event might be raised by
                // a provider.  TopLevelWindow event is raised by UI Automation framework.
                if (ShouldAdviseProviders( l.EventId ))
                {
                    // .. then let the server know about this listener
                    ec.EventHandle = UiaCoreApi.UiaAddEvent(rawEl.RawNode, l.EventId.Id, ec.CallbackDelegate, l.TreeScope, PropertyArrayToIntArray(l.Properties), l.CacheRequest);
                }
            }
        }
 
        private static int[] PropertyArrayToIntArray(AutomationProperty[] properties)
        {
            if (properties == null)
                return null;
            int[] propertiesAsInts = new int[properties.Length];
            for (int i = 0; i < properties.Length; i++)
            {
                propertiesAsInts[i] = properties[i].Id;
            }
            return propertiesAsInts;
        }
 
 
        // RemoveListener - Removes a listener from this client and notifies the UIAutomation server-side
        internal static void RemoveListener( AutomationEvent eventId, AutomationElement el, Delegate eventCallback )
        {
            lock( _classLock )
            {
                if( _listeners != null )
                {
                    bool boundingRectListeners = false; // if not removing BoundingRect listeners no need to do check below
                    bool menuListeners = false; // if not removing MenuOpenedOrClosed listeners no need to do check below
                    bool windowInteracationListeners = false; // if not removing WindowsIntercation listeners no need to do check below
                    bool windowVisualListeners = false; // if not removing WindowsVisual listeners no need to do check below
 
                    for (int i = _listeners.Count - 1; i >= 0; i--)
                    {
                        EventListenerClientSide ec = (EventListenerClientSide)_listeners[i];
                        if( ec.IsListeningFor( eventId, el, eventCallback ) )
                        {
                            EventListener l = ec.EventListener;
 
                            // Only advise UI contexts of events being removed if the event might be raised by
                            // a provider.  TopLevelWindow event is raised by UI Automation framework.
                            if ( ShouldAdviseProviders(eventId) )
                            {
                                // Notify the server-side that this event is no longer interesting
                                try
                                {
                                    ec.EventHandle.Dispose(); // Calls UiaCoreApi.UiaRemoveEvent
                                }
// PRESHARP: Warning - Catch statements should not have empty bodies
#pragma warning disable 6502
                                catch (ElementNotAvailableException)
                                {
                                    // the element is gone already; continue on and remove the listener
                                }
#pragma warning restore 6502
                                finally
                                {
                                    ec.Dispose();
                                }
                            }
 
                            // before removing, check if this delegate was listening for the below events
                            // and see if we can stop clientside WinEvent trackers.
                            if (HasProperty(AutomationElement.BoundingRectangleProperty, l.Properties))
                            {
                                boundingRectListeners = true;
                            }
 
                            if( eventId == AutomationElement.MenuOpenedEvent || eventId == AutomationElement.MenuClosedEvent )
                            {
                                menuListeners = true;
                            }
 
                            if (HasProperty(WindowPattern.WindowInteractionStateProperty, l.Properties))
                            {
                                windowInteracationListeners = true;
                            }
 
                            if (HasProperty(WindowPattern.WindowVisualStateProperty, l.Properties))
                            {
                                windowVisualListeners = true;
                            }
 
                            // delete this one
                            _listeners.RemoveAt( i );
                        }
                    }
 
                    // Check listeners bools to see if clientside listeners can be removed
                    if (boundingRectListeners)
                    {
                        RemovePropertyTracker(AutomationElement.BoundingRectangleProperty, Tracker.BoundingRect);
                    }
 
                    if (menuListeners)
                    {
                        RemoveMenuListeners();
                    }
 
                    if (windowInteracationListeners)
                    {
                        RemovePropertyTracker(WindowPattern.WindowInteractionStateProperty, Tracker.WindowInteractionState);
                    }
 
                    if (windowVisualListeners)
                    {
                        RemovePropertyTracker(WindowPattern.WindowVisualStateProperty, Tracker.WindowVisualState);
                    }
 
                    // See if we can cleanup completely
                    if (_listeners.Count == 0)
                    {
                        // as long as OnWindowShowOrOpen is static can just use new here and get same object instance
                        // (if there's no WindowShowOrOpen listener, this method just returns)
                        RemoveWinEventListener(Tracker.WindowShowOrOpen, new WindowShowOrOpenHandler(OnWindowShowOrOpen));
                        RemoveWinEventListener( Tracker.WindowHideOrClose, new WindowHideOrCloseHandler( OnWindowHideOrClose ) );
 
                        _listeners = null;
                    }
                }
            }
        }
 
        private static void RemovePropertyTracker(AutomationProperty property, Tracker tracker)
        {
            bool foundListener = false;     // assume none
            foreach (EventListenerClientSide l in _listeners)
            {
                if (HasProperty(property, l.EventListener.Properties))
                {
                    foundListener = true;  // delegate is still interested so can't
                    break;                 // stop the WinEvent tracking
                }
            }
            if (!foundListener)
            {
                RemoveWinEventListener(tracker, null);
            }
        }
 
        private static void RemoveMenuListeners()
        {
            bool menuListeners = false; // assume none
            foreach (EventListenerClientSide l in _listeners)
            {
                if (l.EventListener.EventId == AutomationElement.MenuOpenedEvent || l.EventListener.EventId == AutomationElement.MenuClosedEvent)
                {
                    menuListeners = true;  // delegate is still interested so can't
                    break;                       // stop the WinEvent tracking
                }
            }
            if (!menuListeners)
            {
                // as long as OnMenuEvent is static can just use new here and get same object instance
                RemoveWinEventListener(Tracker.MenuOpenedOrClosed, new MenuHandler(OnMenuEvent));
            }
        }
 
        // RemoveAllListeners - Removes all listeners from this client and notifies the
        // UIAccess server
        internal static void RemoveAllListeners()
        {
            lock (_classLock)
            {
                if (_listeners == null)
                    return;
 
                // Stop all WinEvent tracking
                StopWinEventTracking();
 
                // Must remove from back to front when calling RemoveAt because ArrayList
                // elements are compressed after each RemoveAt.
                for (int i = _listeners.Count - 1; i >= 0; i--)
                {
                    EventListenerClientSide ec = (EventListenerClientSide)_listeners[i];
 
                    // Notify the server-side UIAccess that this event is no longer interesting
                    EventListener l = ec.EventListener;
                    // Only advise UI contexts of events being removed if the event might be raised by
                    // a provider.  TopLevelWindow event is raised by UI Automation framework.
                    if ( ShouldAdviseProviders(l.EventId) )
                    {
                        ec.EventHandle.Dispose(); // Calls RemoveEvent
                    }
                    // delete this one
                    _listeners.RemoveAt(i);
                }
 
                _listeners = null;
                CheckStopCallbackQueueing();
            }
        }
 
        #endregion Internal Methods
 
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        // return the queue class instance
        internal static QueueProcessor CBQ
        {
            get
            {
                return _callbackQueue;
            }
        }
 
        #endregion Internal Properties
 
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // check queue is started, or start if necessary (always called w/in a lock)
        private static void CheckStartCallbackQueueing()
        {
            if (!_isBkgrdThreadRunning)
            {
                _isBkgrdThreadRunning = true;
               _callbackQueue = new QueueProcessor();
               _callbackQueue.StartOnThread();
            }
        }
 
 
        // check if queue is still needed, stop if necessary (always called w/in a lock)
        private static void CheckStopCallbackQueueing()
        {
            // anything to stop?
            if (!_isBkgrdThreadRunning)
                return;
 
            // if there are listeners then can't stop
            if (_listeners != null)
                return;
 
            // Are any WinEvents currently being tracked for this client?
            foreach (WinEventWrap eventWrapper in _winEventTrackers)
            {
                if (eventWrapper != null)
                {
                    return;
                }
            }
 
            // OK to stop the queue now
            _isBkgrdThreadRunning = false;
            _callbackQueue.PostQuit();
            // Intentionally not setting _callbackQueue null here; don't want to mess with it from this thread.
        }
 
        // StopWinEventTracking is called when we just want to quit (RemoveAllListeners)
        private static void StopWinEventTracking()
        {
            int i;
            for (i=0; i<(int)Tracker.NumEventTrackers; i++)
            {
                WinEventWrap eventWrapper = _winEventTrackers[i];
                if (eventWrapper != null)
                {
                    eventWrapper.StopListening();
                    _winEventTrackers[i] = null;
                }
            }
        }
 
        // Raise events for rawEl
        internal static void RaiseEventInThisClientOnly(AutomationEvent eventId, AutomationElement rawEl, AutomationEventArgs e)
        {
            // This version of RaiseEventInThisClientOnly can be called with a local (proxied) or remote (native)AutomationElement
            lock (_classLock)
            {
                if ( _listeners == null )
                    return;
 
                AutomationElement el = rawEl;
                foreach (EventListenerClientSide listener in _listeners)
                {
                    // Is this event a type this listener is interested in?
                    if (listener.EventListener.EventId == eventId)
                    {
                        // Did this event happen on an element this listener is interested in?
                        if (rawEl == null || listener.WithinScope( rawEl ))
                        {
                            UiaCoreApi.UiaCacheRequest cacheRequest = listener.EventListener.CacheRequest;
                            CBQ.PostWorkItem(new ClientSideQueueItem(listener.ClientCallback, el, cacheRequest, e));
                        }
                    }
                }
            }
        }
 
        // Raise events for the element that has RuntimeId == rid (special case for events where the element is no longer available)
        internal static void RaiseEventInThisClientOnly( AutomationEvent eventId, int [] rid, AutomationEventArgs e)
        {
            // This version of RaiseEventInThisClientOnly can be called with a local (proxied) or remote (native)AutomationElement
            lock ( _classLock )
            {
                if ( _listeners == null )
                    return;
 
                foreach ( EventListenerClientSide listener in _listeners )
                {
                    // Is this event a type this listener is interested in?
                    if ( listener.EventListener.EventId == eventId )
                    {
                        // Did this event happen on an element this listener is interested in?
                        if ( listener.WithinScope( rid ) )
                        {
                            CBQ.PostWorkItem(new ClientSideQueueItem(listener.ClientCallback, null, null, e));
                        }
                    }
                }
            }
        }
 
        // GetNewRootTracker - Returns a new WinEvent wrapped object for events that are
        // always based on the RootElement (e.g. AutomationFocusChanged or TopWindow events)
        private static WinEventWrap GetNewRootTracker(Tracker idx)
        {
            if (idx == Tracker.Focus)
            {
                return new FocusTracker();
            }
 
            Debug.Assert(false, "GetNewRootTracker internal error: Unexpected Tracker value!");
            return null;
        }
 
        // AddRootListener - Add a listener for an event whose reference is always the
        // root and scope is all elements
        private static void AddRootListener(Tracker idx, Delegate eventCallback, EventListener l)
        {
            lock ( _classLock )
            {
                // Add this listener to client-side store of listeners and give the server
                // a chance to enable accessibility for this event
                AddListener( AutomationElement.RootElement, eventCallback, l );
 
                // Track WinEvents
                WinEventWrap eventWrapper = _winEventTrackers[(int)idx];
 
                if ( eventWrapper == null )
                {
                    // First time create a WinEvent tracker and start listening
                    AddWinEventListener( idx, GetNewRootTracker( idx ) );
                }
                else
                {
                    // Subsequent times just add the callback to the existing WinEvent
                    eventWrapper.AddCallback( eventCallback );
                }
            }
        }
 
        // RemoveRootListener - Remove a listener for an event whose reference is always
        // the root and scope is all elements
        private static void RemoveRootListener(AutomationEvent eventId, Tracker idx, Delegate eventCallback)
        {
            lock (_classLock)
            {
                RemoveListener(eventId, AutomationElement.RootElement, eventCallback);
                RemoveWinEventListener(idx, eventCallback);
            }
        }
 
        // AddWinEventListener - add an event callback for a global listener
        private static void AddWinEventListener(Tracker idx, WinEventWrap eventWrapper)
        {
            // make sure we can queue items
            CheckStartCallbackQueueing();
 
            _winEventTrackers[(int)idx] = eventWrapper;
            _callbackQueue.PostSyncWorkItem(new WinEventQueueItem(eventWrapper, WinEventQueueItem.StartListening));
        }
 
        // RemoveWinEventListener - remove an event callback for a global listener
        private static void RemoveWinEventListener(Tracker idx, Delegate eventCallback)
        {
            WinEventWrap eventWrapper = _winEventTrackers[(int)idx];
            if (eventWrapper == null)
                return;
 
            bool fRemovedLastListener = eventWrapper.RemoveCallback(eventCallback);
            if (fRemovedLastListener)
            {
                _callbackQueue.PostSyncWorkItem(new WinEventQueueItem(eventWrapper, WinEventQueueItem.StopListening));
                _winEventTrackers[(int)idx] = null;
 
                CheckStopCallbackQueueing();
            }
        }
 
        // HasProperty - helper to check for a property in an AutomationProperty array
        private static bool HasProperty(AutomationProperty p, AutomationProperty [] properties)
        {
            if (properties == null)
                return false;
 
            foreach (AutomationProperty p1 in properties)
            {
                if (p1 == p)
                {
                    return true;
                }
            }
            return false;
        }
 
        // OnWindowHideOrClose - Called by the WindowHideOrCloseTracker class when UI is hidden or destroyed
        private static void OnWindowHideOrClose( IntPtr hwnd, AutomationElement rawEl, int [] runtimeId )
        {
            bool doWindowClosedEvent = false;
            bool doStructureChangedEvent = false;
 
            lock ( _classLock )
            {
                if (_listeners != null)
                {
                    // if an hwnd is hidden or closed remove event listeners for the window's provider
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        EventListenerClientSide ec = (EventListenerClientSide)_listeners[i];
 
                        EventListener l = ec.EventListener;
                        if ( l.EventId == WindowPattern.WindowClosedEvent )
                            doWindowClosedEvent = true;
                        if ( l.EventId == AutomationElement.StructureChangedEvent )
                            doStructureChangedEvent = true;
 
                        // Only advise UI contexts if the provider still exists
                        // (but keep looking to see if need to do a WindowClosedEvent)
                        if (rawEl == null)
                            continue;
 
                        // Only advise UI contexts if the provider might raise that event.
                        if (!ShouldAdviseProviders(l.EventId))
                            continue;
 
                        // Only advise UI contexts if the element is w/in scope of the reference element
                        if (!ec.WithinScope(rawEl))
                            continue;
 
                        // Notify the server-side that this event is no longer interesting
                        UiaCoreApi.UiaEventRemoveWindow(ec.EventHandle, hwnd);
                    }
                }
            }
 
            // Piggy-back on the listener for Windows hiding or closing to raise WindowClosed and StructureChanged events.
            // When the hwnd behind rawEl is being destroyed, it can't be determined that rawEl once had the
            // WindowPattern interface.  Therefore raise an event for any window close.
            if ( doWindowClosedEvent )
            {
                // When the hwnd is just hidden, rawEl will not be null, so can test if this would support WindowPattern
                // and throw this event away if the window doesn't support that CP
                if ( rawEl != null && !HwndProxyElementProvider.IsWindowPatternWindow( NativeMethods.HWND.Cast( hwnd ) ) )
                    return;
 
                // Go ahead and raise a client-side only WindowClosedEvent (if anyone is listening)
                WindowClosedEventArgs e = new WindowClosedEventArgs( runtimeId );
                RaiseEventInThisClientOnly(WindowPattern.WindowClosedEvent, runtimeId, e);
            }
            if ( doStructureChangedEvent )
            {
                // Raise an event for structure changed.  This element has essentially gone away so there isn't an
                // opportunity to do filtering here.  So, just like WindowClosed, this event will be very noisy.
                StructureChangedEventArgs e = new StructureChangedEventArgs( StructureChangeType.ChildRemoved, runtimeId );
                RaiseEventInThisClientOnly(AutomationElement.StructureChangedEvent, runtimeId, e);
            }
        }
 
        // OnWindowShowOrOpen - Called by the WindowShowOrOpenTracker class when UI is shown or created
        private static void OnWindowShowOrOpen( IntPtr hwnd, AutomationElement rawEl )
        {
            bool doWindowOpenedEvent = false;
            bool doStructureChangedEvent = false;
 
            lock ( _classLock )
            {
                if (_listeners != null)
                {
                    // if rawEl is w/in the scope of any listeners then register for events in the new UI
                    for (int i = 0; i < _listeners.Count; i++)
                    {
                        EventListenerClientSide ec = (EventListenerClientSide)_listeners[i];
 
                        EventListener l = ec.EventListener;
                        if ( l.EventId == WindowPattern.WindowOpenedEvent )
                            doWindowOpenedEvent = true;
                        if ( l.EventId == AutomationElement.StructureChangedEvent )
                            doStructureChangedEvent = true;
 
                        // Only advise UI contexts if the provider might raise that event.
                        if (!ShouldAdviseProviders( l.EventId ))
                            continue;
 
                        // Only advise UI contexts if the element is w/in scope of the reference element
                        if (!ec.WithinScope( rawEl ))
                            continue;
 
                        // Notify the server side
                        UiaCoreApi.UiaEventAddWindow(ec.EventHandle, hwnd);
                    }
                }
            }
 
            // Piggy-back on the listener for Windows hiding or closing to raise WindowClosed and StructureChanged events.
            if ( doWindowOpenedEvent )
            {
                if ( HwndProxyElementProvider.IsWindowPatternWindow( NativeMethods.HWND.Cast( hwnd ) ) )
                {
                    // Go ahead and raise a client-side only WindowOpenedEvent (if anyone is listening)
                    AutomationEventArgs e = new AutomationEventArgs( WindowPattern.WindowOpenedEvent );
                    RaiseEventInThisClientOnly( WindowPattern.WindowOpenedEvent, rawEl, e);
                }
            }
            if ( doStructureChangedEvent )
            {
                // Filter on the control elements.  Otherwise, this is extremely noisy.  Consider not filtering if there is feedback.
                //ControlType ct = (ControlType)rawEl.GetPropertyValue( AutomationElement.ControlTypeProperty );
                //if ( ct != null )
                {
                    // Last,raise an event for structure changed
                    StructureChangedEventArgs e = new StructureChangedEventArgs( StructureChangeType.ChildAdded, rawEl.GetRuntimeId() );
                    RaiseEventInThisClientOnly(AutomationElement.StructureChangedEvent, rawEl, e);
                }
            }
        }
 
        // OnMenuEvent - Called by MenuTracker class
        private static void OnMenuEvent( AutomationElement rawEl, bool menuHasOpened )
        {
            AutomationEvent eventId = menuHasOpened ? AutomationElement.MenuOpenedEvent : AutomationElement.MenuClosedEvent;
            AutomationEventArgs e = new AutomationEventArgs( eventId );
 
            RaiseEventInThisClientOnly(eventId, rawEl, e);
        }
 
 
        private static bool ShouldAdviseProviders( AutomationEvent eventId )
        {
            foreach (AutomationEvent ev in _doNotShouldAdviseProviders)
            {
                if (ev == eventId)
                    return false;
            }
 
            return true;
        }
 
        #endregion Private Methods
 
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        // Indices into ClientEventManager._winEventTrackers.  How wrapped WinEvents are added
        // and removed is common code; using an array makes it easier to add, remove and do
        // cleanup on client exit.
        private enum Tracker
        {
            Focus = 0,
            WindowShowOrOpen,
            WindowHideOrClose,
            BoundingRect,
            MenuOpenedOrClosed,
            WindowInteractionState,
            WindowVisualState,
            // insert additional indices here...
            NumEventTrackers,
        }
 
        // If WindowPattern was exposed by the proxies then the WindowPattern code here
        // should change to remove WindowPattern events from those UIAutomation client-side raises.
        private static AutomationEvent[] _doNotShouldAdviseProviders = new AutomationEvent[] {
            WindowPattern.WindowOpenedEvent, WindowPattern.WindowClosedEvent
        };
 
        private static WinEventWrap [] _winEventTrackers = new WinEventWrap[(int)Tracker.NumEventTrackers];
 
        private static QueueProcessor _callbackQueue;      // callbacks are queued on this class to avoid deadlocks
        private static bool _isBkgrdThreadRunning = false; // is there a background thread for queueing and recieving WinEvents?
        private static ArrayList _listeners;               // data representing events the client is listening for
        private static readonly object _classLock = new object();   // use lock object vs typeof(class) for perf reasons
 
        #endregion Private Fields
    }
}