File: System\Windows\Automation\AutomationElement.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: Main class used by Automation clients, represents a UI element
 
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System;
using System.Runtime.Serialization;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.ComponentModel;
using MS.Win32;
using MS.Internal.Automation;
using System.Runtime.InteropServices;
 
#if EVENT_TRACING_PROPERTY
using Microsoft.Win32.Diagnostics;
#endif
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
namespace System.Windows.Automation
{
    /// <summary>
    /// Represents an element in the UIAutomation tree.
    /// </summary>
#if (INTERNAL_COMPILE)
    internal sealed class AutomationElement  //: IDisposable 
#else
    public sealed class AutomationElement
#endif
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // Private ctor, used mostly by CacheHelper when reconstructing AutomationElements from
        // a CacheResponse.
        internal AutomationElement(SafeNodeHandle hnode, object[,] cachedValues, int cachedValuesIndex, UiaCoreApi.UiaCacheRequest request)
        {
            _hnode = hnode; // Can be IntPtr.Zero for a lightweight object
            _cachedValues = cachedValues; // Can be null if there are no cached properties for this node
            _cachedValuesIndex = cachedValuesIndex;
            _request = request;
 
            // Set RuntimeId (if available; 'as int[]' filters out AutomationElement.NotAvailable)
            _runtimeId = LookupCachedValue(AutomationElement.RuntimeIdProperty, false, true) as int[];
 
            // Anytime an element is packaged up it should always go through a CacheRequest and that always grabs 
            // RuntimeId so don't check here if _runtimeId is null.  If we get here and there is no RuntimeId the implication 
            // is that pre-fetching isn't working.  Possible edge case: During capturing the properties we get partial
            // properties but don't abandon the pre-fetch.  Should re-visit this scenario in a special Drt.
 
            // One scenario that allows for null runtimeID - doing UpdatedCache() on a node and asking only
            // for children - gives us back a placeholder node that only has valid .CachedChildren,
            // the node itself doesn't have any cached properties or a node.
 
            // Since null is a valid value for these, we need another value to
            // indicate that they were not requested - it's a bit obscure, but
            // 'this' works well here, since these can never have it as legal value.
            _cachedParent = this;
            _cachedFirstChild = this;
            _cachedNextSibling = this;
        }
 
        /// <summary>
        /// Overrides Object.Finalize
        /// </summary>
        ~AutomationElement()
        {
        }
        
        // Used by methods that return non-cached AutomationElements - examples currently include
        // AutomationElements returned as properties (SelecitonContainer, RowHeaders).
        internal static AutomationElement Wrap(SafeNodeHandle hnode)
        {
            if (hnode == null || hnode.IsInvalid)
            {
                return null;
            }
 
            return new AutomationElement(hnode, null, 0, null);
        }
 
       #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Public Constants / Readonly Fields
        //
        //------------------------------------------------------
 
        #region Public Constants and Readonly Fields
 
        /// <summary>
        /// Indicates that a element does not support the requested value
        /// </summary>
        public static readonly object NotSupported = AutomationElementIdentifiers.NotSupported;
 
        /// <summary>Property ID: Indicates that this element should be included in the Control view of the tree</summary>
        public static readonly AutomationProperty IsControlElementProperty = AutomationElementIdentifiers.IsControlElementProperty;
 
        /// <summary>Property ID: The ControlType of this Element</summary>
        public static readonly AutomationProperty ControlTypeProperty = AutomationElementIdentifiers.ControlTypeProperty;
 
        /// <summary>Property ID: NativeWindowHandle - Window Handle, if the underlying control is a Window</summary>
        public static readonly AutomationProperty IsContentElementProperty = AutomationElementIdentifiers.IsContentElementProperty;
 
        /// <summary>Property ID: The AutomationElement that labels this element</summary>
        public static readonly AutomationProperty LabeledByProperty = AutomationElementIdentifiers.LabeledByProperty;
 
        /// <summary>Property ID: NativeWindowHandle - Window Handle, if the underlying control is a Window</summary>
        public static readonly AutomationProperty NativeWindowHandleProperty = AutomationElementIdentifiers.NativeWindowHandleProperty;
 
        /// <summary>Property ID: AutomationId - An identifier for an element that is unique within its containing element.</summary>
        public static readonly AutomationProperty AutomationIdProperty = AutomationElementIdentifiers.AutomationIdProperty;
 
        /// <summary>Property ID: ItemType - An application-level property used to indicate what the items in a list represent.</summary> 
        public static readonly AutomationProperty ItemTypeProperty = AutomationElementIdentifiers.ItemTypeProperty;
 
        /// <summary>Property ID: True if the control is a password protected field. </summary>
        public static readonly AutomationProperty IsPasswordProperty = AutomationElementIdentifiers.IsPasswordProperty;
 
        /// <summary>Property ID: Localized control type description (eg. "Button")</summary>
        public static readonly AutomationProperty LocalizedControlTypeProperty = AutomationElementIdentifiers.LocalizedControlTypeProperty;
 
        /// <summary>Property ID: name of this instance of control</summary>
        public static readonly AutomationProperty NameProperty = AutomationElementIdentifiers.NameProperty;
 
        /// <summary>Property ID: Hot-key equivalent for this command item. (eg. Ctrl-P for Print)</summary>
        public static readonly AutomationProperty AcceleratorKeyProperty = AutomationElementIdentifiers.AcceleratorKeyProperty;
 
        /// <summary>Property ID: Keys used to move focus to this control</summary>
        public static readonly AutomationProperty AccessKeyProperty = AutomationElementIdentifiers.AccessKeyProperty;
 
        /// <summary>Property ID: HasKeyboardFocus</summary>
        public static readonly AutomationProperty HasKeyboardFocusProperty = AutomationElementIdentifiers.HasKeyboardFocusProperty;
 
        /// <summary>Property ID: IsKeyboardFocusable</summary>
        public static readonly AutomationProperty IsKeyboardFocusableProperty = AutomationElementIdentifiers.IsKeyboardFocusableProperty;
 
        /// <summary>Property ID: Enabled</summary>
        public static readonly AutomationProperty IsEnabledProperty = AutomationElementIdentifiers.IsEnabledProperty;
 
        /// <summary>Property ID: BoundingRectangle - bounding rectangle</summary>
        public static readonly AutomationProperty BoundingRectangleProperty = AutomationElementIdentifiers.BoundingRectangleProperty;
 
        /// <summary>Property ID: id of process that this element lives in</summary>
        public static readonly AutomationProperty ProcessIdProperty = AutomationElementIdentifiers.ProcessIdProperty;
 
        /// <summary>Property ID: RuntimeId - runtime unique ID</summary>
        public static readonly AutomationProperty RuntimeIdProperty = AutomationElementIdentifiers.RuntimeIdProperty;
 
        /// <summary>Property ID: ClassName - name of underlying class - implementation dependant, but useful for test</summary>
        public static readonly AutomationProperty ClassNameProperty = AutomationElementIdentifiers.ClassNameProperty;
 
        /// <summary>Property ID: HelpText - brief description of what this control does</summary>
        public static readonly AutomationProperty HelpTextProperty = AutomationElementIdentifiers.HelpTextProperty;
 
        /// <summary>Property ID: ClickablePoint - Set by provider, used internally for GetClickablePoint</summary>
        public static readonly AutomationProperty ClickablePointProperty = AutomationElementIdentifiers.ClickablePointProperty;
 
        /// <summary>Property ID: Culture - Returns the culture that provides information about the control's content.</summary>
        public static readonly AutomationProperty CultureProperty = AutomationElementIdentifiers.CultureProperty;
 
        /// <summary>Property ID: Offscreen - Determined to be not-visible to the sighted user</summary>
        public static readonly AutomationProperty IsOffscreenProperty = AutomationElementIdentifiers.IsOffscreenProperty;
 
        /// <summary>Property ID: Orientation - Identifies whether a control is positioned in a specfic direction</summary>
        public static readonly AutomationProperty OrientationProperty = AutomationElementIdentifiers.OrientationProperty;
 
        /// <summary>Property ID: FrameworkId - Identifies the underlying UI framework's name for the element being accessed</summary>
        public static readonly AutomationProperty FrameworkIdProperty = AutomationElementIdentifiers.FrameworkIdProperty;
 
        /// <summary>Property ID: IsRequiredForForm - Identifies weather an edit field is required to be filled out on a form</summary>
        public static readonly AutomationProperty IsRequiredForFormProperty = AutomationElementIdentifiers.IsRequiredForFormProperty;
 
        /// <summary>Property ID: ItemStatus - Identifies the status of the visual representation of a complex item</summary>
        public static readonly AutomationProperty ItemStatusProperty = AutomationElementIdentifiers.ItemStatusProperty;
 
        /// <summary>
        /// Property ID: SizeOfSet - Describes the count of automation elements in a group or set that are considered to be siblings.
        /// Works in coordination with the PositionInSet property to describe the count of items in the set.
        /// </summary>
        public static readonly AutomationProperty SizeOfSetProperty = AutomationElementIdentifiers.SizeOfSetProperty;
 
        /// <summary>
        /// Property ID: PositionInSet - Describes the ordinal location of an automation element within a set of elements which are considered to be siblings.
        /// Works in coordination with the SizeOfSet property to describe the ordinal location in the set.
        /// </summary>
        public static readonly AutomationProperty PositionInSetProperty = AutomationElementIdentifiers.PositionInSetProperty;
 
        /// <summary>
        /// Property ID: HeadingLevel - Describes the heading level of an element.
        /// </summary>
        public static readonly AutomationProperty HeadingLevelProperty = AutomationElementIdentifiers.HeadingLevelProperty;
 
        /// <summary>
        /// Property ID: IsDialog - Identifies if the automation element is a dialog.
        /// </summary>
        public static readonly AutomationProperty IsDialogProperty = AutomationElementIdentifiers.IsDialogProperty;
 
        #region IsNnnnPatternAvailable properties
        /// <summary>Property that indicates whether the DockPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsDockPatternAvailableProperty = AutomationElementIdentifiers.IsDockPatternAvailableProperty;
        /// <summary>Property that indicates whether the ExpandCollapsePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsExpandCollapsePatternAvailableProperty = AutomationElementIdentifiers.IsExpandCollapsePatternAvailableProperty;
        /// <summary>Property that indicates whether the GridItemPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsGridItemPatternAvailableProperty = AutomationElementIdentifiers.IsGridItemPatternAvailableProperty;
        /// <summary>Property that indicates whether the GridPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsGridPatternAvailableProperty = AutomationElementIdentifiers.IsGridPatternAvailableProperty;
        /// <summary>Property that indicates whether the InvokePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsInvokePatternAvailableProperty = AutomationElementIdentifiers.IsInvokePatternAvailableProperty;
        /// <summary>Property that indicates whether the MultipleViewPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsMultipleViewPatternAvailableProperty = AutomationElementIdentifiers.IsMultipleViewPatternAvailableProperty;
        /// <summary>Property that indicates whether the RangeValuePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsRangeValuePatternAvailableProperty = AutomationElementIdentifiers.IsRangeValuePatternAvailableProperty;
        /// <summary>Property that indicates whether the SelectionItemPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsSelectionItemPatternAvailableProperty = AutomationElementIdentifiers.IsSelectionItemPatternAvailableProperty;
        /// <summary>Property that indicates whether the SelectionPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsSelectionPatternAvailableProperty = AutomationElementIdentifiers.IsSelectionPatternAvailableProperty;
        /// <summary>Property that indicates whether the ScrollPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsScrollPatternAvailableProperty = AutomationElementIdentifiers.IsScrollPatternAvailableProperty;
        /// <summary>Property that indicates whether the SynchronizedInputPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsSynchronizedInputPatternAvailableProperty = AutomationElementIdentifiers.IsSynchronizedInputPatternAvailableProperty;
        /// <summary>Property that indicates whether the ScrollItemPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsScrollItemPatternAvailableProperty = AutomationElementIdentifiers.IsScrollItemPatternAvailableProperty;
        /// <summary>Property that indicates whether the VirtualizedItemPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsVirtualizedItemPatternAvailableProperty = AutomationElementIdentifiers.IsVirtualizedItemPatternAvailableProperty;
        /// <summary>Property that indicates whether the ItemContainerPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsItemContainerPatternAvailableProperty = AutomationElementIdentifiers.IsItemContainerPatternAvailableProperty;
        /// <summary>Property that indicates whether the TablePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsTablePatternAvailableProperty = AutomationElementIdentifiers.IsTablePatternAvailableProperty;
        /// <summary>Property that indicates whether the TableItemPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsTableItemPatternAvailableProperty = AutomationElementIdentifiers.IsTableItemPatternAvailableProperty;
        /// <summary>Property that indicates whether the TextPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsTextPatternAvailableProperty = AutomationElementIdentifiers.IsTextPatternAvailableProperty;
        /// <summary>Property that indicates whether the TogglePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsTogglePatternAvailableProperty = AutomationElementIdentifiers.IsTogglePatternAvailableProperty;
        /// <summary>Property that indicates whether the TransformPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsTransformPatternAvailableProperty = AutomationElementIdentifiers.IsTransformPatternAvailableProperty;
        /// <summary>Property that indicates whether the ValuePattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsValuePatternAvailableProperty = AutomationElementIdentifiers.IsValuePatternAvailableProperty;
        /// <summary>Property that indicates whether the WindowPattern is available for this AutomationElement</summary>
        public static readonly AutomationProperty IsWindowPatternAvailableProperty = AutomationElementIdentifiers.IsWindowPatternAvailableProperty;
        #endregion IsNnnnPatternAvailable properties
 
        #region Events
 
        /// <summary>Event ID: ToolTipOpenedEvent - indicates a tooltip has appeared</summary>
        public static readonly AutomationEvent ToolTipOpenedEvent = AutomationElementIdentifiers.ToolTipOpenedEvent;
 
        /// <summary>Event ID: ToolTipClosedEvent - indicates a tooltip has closed.</summary>
        public static readonly AutomationEvent ToolTipClosedEvent = AutomationElementIdentifiers.ToolTipClosedEvent;
 
        /// <summary>Event ID: StructureChangedEvent - used mainly by servers to notify of structure changed events.  Clients use AddStructureChangedHandler.</summary>
        public static readonly AutomationEvent StructureChangedEvent = AutomationElementIdentifiers.StructureChangedEvent;
 
        /// <summary>Event ID: MenuOpened - Indicates an a menu has opened.</summary>
        public static readonly AutomationEvent MenuOpenedEvent = AutomationElementIdentifiers.MenuOpenedEvent;
 
        /// <summary>Event ID: AutomationPropertyChangedEvent - used mainly by servers to notify of property changes. Clients use AddPropertyChangedListener.</summary>
        public static readonly AutomationEvent AutomationPropertyChangedEvent = AutomationElementIdentifiers.AutomationPropertyChangedEvent;
 
        /// <summary>Event ID: AutomationFocusChangedEvent - used mainly by servers to notify of focus changed events.  Clients use AddAutomationFocusChangedListener.</summary>
        public static readonly AutomationEvent AutomationFocusChangedEvent = AutomationElementIdentifiers.AutomationFocusChangedEvent;
 
        /// <summary>Event ID: AsyncContentLoadedEvent - indicates an async content loaded event.</summary>
        public static readonly AutomationEvent AsyncContentLoadedEvent = AutomationElementIdentifiers.AsyncContentLoadedEvent;
 
        /// <summary>Event ID: MenuClosed - Indicates an a menu has closed.</summary>
        public static readonly AutomationEvent MenuClosedEvent = AutomationElementIdentifiers.MenuClosedEvent;
 
        /// <summary>Event ID: LayoutInvalidated - Indicates that many element locations/extents/offscreenedness have changed.</summary>
        public static readonly AutomationEvent LayoutInvalidatedEvent = AutomationElementIdentifiers.LayoutInvalidatedEvent;
 
        /// <summary>Event ID: Notification - used mainly by servers to raise a generic notification.</summary>
        public static readonly AutomationEvent NotificationEvent = AutomationElementIdentifiers.NotificationEvent;
 
        /// <summary>Event ID: ActiveTextPositionChanged - Indicates that the active position within a text element has changed.</summary>
        public static readonly AutomationEvent ActiveTextPositionChangedEvent = AutomationElementIdentifiers.ActiveTextPositionChangedEvent;
 
        #endregion Events
 
        #endregion Public Constants and Readonly Fields
 
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        #region Equality
        /// <summary>
        /// Overrides Object.Equals
        /// </summary>
        /// <param name="obj">The Object to compare with the current object</param>
        /// <returns>true if the AutomationElements refer to the same UI; otherwise, false</returns>
        /// <remarks>Note that two AutomationElements that compare as equal may contain
        /// different cached information from different points in time; the equality check only
        /// tests that the AutomationElements refer to the same underlying UI.
        /// </remarks>
        public override bool Equals(object obj)
        {
            AutomationElement el = obj as AutomationElement;
            if (obj == null || el == null)
                return false;
 
            return Misc.Compare(this, el);
        }
 
        /// <summary>
        /// Overrides Object.GetHashCode
        /// </summary>
        /// <returns>An integer that represents the hashcode for this AutomationElement</returns>
        public override int GetHashCode()
        {
            int[] id = GetRuntimeId();
            int hash = 0;
 
            if (id == null)
            {
                // Hash codes need to be unique if the runtime ids are null we will end up 
                // handing out duplicates so throw an exception.
                throw new InvalidOperationException(SR.OperationCannotBePerformed);
            }
 
            for (int i = 0; i < id.Length; i++)
            {
                hash = (hash * 2) ^ id[i];
            }
 
            return hash;
        }
 
        /// <summary>
        /// Tests whether two AutomationElement objects are equivalent
        /// </summary>
        /// <param name="left">The AutomationElement that is to the left of the equality operator</param>
        /// <param name="right">The AutomationElement that is to the right of the equality operator</param>
        /// <returns>This operator returns true if two AutomationElements refer to the same UI; otherwise false</returns>
        /// <remarks>Note that two AutomationElements that compare as equal may contain
        /// different cached information from different points in time; the equality check only
        /// tests that the AutomationElements refer to the same underlying UI.
        /// </remarks>
        public static bool operator ==(AutomationElement left, AutomationElement right)
        {
            if ((object)left == null)
                return (object)right == null;
 
            if ((object)right == null)
                return (object)left == null;
 
            return left.Equals(right);
        }
 
        /// <summary>
        /// Tests whether two AutomationElement objects are not equivalent
        /// </summary>
        /// <param name="left">The AutomationElement that is to the left of the inequality operator</param>
        /// <param name="right">The AutomationElement that is to the right of the inequality operator</param>
        /// <returns>This operator returns true if two AutomationElements refer to different UI; otherwise false</returns>
        public static bool operator !=(AutomationElement left, AutomationElement right)
        {
            return !(left == right);
        }
        #endregion Equality
 
 
        /// <summary>
        /// Returns an array of ints that uniquely identifies the UI element that this object represents.
        /// Caller should treat the array as an opaque value; do not attempt to analyze it or pick it apart,
        /// the format may change in future.
        /// 
        /// These identifies are only guaranteed to be unique on a given desktop.
        /// Identifiers may be recycled over time.
        /// </summary>
        public int[] GetRuntimeId()
        {
            if (_runtimeId != null)
                return _runtimeId;
 
            //Not true - some AE's from properties and event args (eg. SelectionItem.SelectionContainer,
            //and FocuEventArgs's previousFocus) don't currently come through CacheReqest
            //Debug.Assert(false, "Should always have runtimeID from cache at ctor.");
 
            // false -> return null (instead of throwing) if not available; true->wrap
            int [] val = LookupCachedValue(AutomationElement.RuntimeIdProperty, false, true) as int[];
            if (val != null)
            {
                _runtimeId = val;
                return _runtimeId;
            }
 
            // Possible that we got this element from a path that doesn't have prefetch
            // enabled - fall back to getting RuntimeId the slow cross-proc way for now
            // Also possible that this was called on an empty element - eg. where someone
            // use TreeScope.Children to get just the children, but not any info for this
            // element. CheckElement() will throw an exception in that case...
            CheckElement();
 
            _runtimeId = UiaCoreApi.UiaGetRuntimeId(_hnode);
            return _runtimeId;
        }
 
        /// <summary>
        /// Get element at specified point on current desktop
        /// </summary>
        /// <param name="pt">point in screen coordinates</param>
        /// <returns>element at specified point</returns>
        public static AutomationElement FromPoint(Point pt)
        {
            return DrillForPointOrFocus(true, pt, CacheRequest.CurrentUiaCacheRequest);
        }
 
        /// <summary>
        /// Get element from specified HWND
        /// </summary>
        /// <param name="hwnd">Handle of window to get element for</param>
        /// <returns>element representing root node of specified window</returns>
        public static AutomationElement FromHandle(IntPtr hwnd)
        {
            Misc.ValidateArgument(hwnd != IntPtr.Zero, nameof(SR.HwndMustBeNonNULL));
 
            SafeNodeHandle hnode = UiaCoreApi.UiaNodeFromHandle(hwnd);
            if (hnode.IsInvalid)
            {
                return null;
            }
 
            UiaCoreApi.UiaCacheRequest cacheRequest = CacheRequest.CurrentUiaCacheRequest;
            // Don't do any normalization when getting updated cache...
            UiaCoreApi.UiaCacheResponse response = UiaCoreApi.UiaGetUpdatedCache(hnode, cacheRequest, UiaCoreApi.NormalizeState.None, null);
            // should we release hnode?
            return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
        }
 
        /// <summary>
        /// Converts a local IRawElementProvider implementation to an AutomationElement.
        /// </summary>
        /// <param name="localImpl">Local object implementing IRawElementProvider</param>
        /// <returns>A corresponding AutomationElement for the impl parameter</returns>
        /// <remarks>This would be used by a client helper library that wanted
        /// to allow its callers to access its own native element type via PAW.
        /// For example, the Windows Client Platform uses its own Element type, but
        /// uses this iternally so that it can had back an AutomationElement to clients
        /// that want to get an AutomationElement directly from an Element.
        /// </remarks>
        public static AutomationElement FromLocalProvider(IRawElementProviderSimple localImpl)
        {
            ArgumentNullException.ThrowIfNull(localImpl);
 
            return AutomationElement.Wrap(UiaCoreApi.UiaNodeFromProvider(localImpl));
        }
 
 
 
        /// <summary>
        /// Get current value of specified property from an element.
        /// </summary>
        /// <param name="property">AutomationProperty that identifies the property</param>
        /// <returns>Returns value of specified property</returns>
        /// <remarks>
        /// If the specified property is not explicitly supported by the target UI,
        /// a default value will be returned. For example, if the target UI doesn't
        /// support the AutomationElement.NameProperty, calling GetCurrentPropertyValue
        /// for that property will return an empty string.
        /// 
        /// This API gets the current value of the property at this point in time,
        /// without checking the cache. For some types of UI, this API will incur
        /// a cross-process performance hit. To access values in this AutomationElement's
        /// cache, use GetCachedPropertyValue instead.
        /// </remarks>
        public object GetCurrentPropertyValue(AutomationProperty property)
        {
            return GetCurrentPropertyValue(property, false);
        }
 
        /// <summary>
        /// Get the current value of specified property from an element.
        /// </summary>
        /// <param name="property">AutomationProperty that identifies the property</param>
        /// <param name="ignoreDefaultValue">Specifies whether a default value should be
        /// ignored if the specified property is supported by the target UI</param>
        /// <returns>Returns value of specified property, or AutomationElement.NotSupported</returns>
        /// <remarks>
        /// This API gets the current value of the property at this point in time,
        /// without checking the cache. For some types of UI, this API will incur
        /// a cross-process performance hit. To access values in this AutomationElement's
        /// cache, use GetCachedPropertyValue instead.
        /// </remarks>
        public object GetCurrentPropertyValue(AutomationProperty property, bool ignoreDefaultValue)
        {
            ArgumentNullException.ThrowIfNull(property);
            CheckElement();
 
            AutomationPropertyInfo pi;
            if (!Schema.GetPropertyInfo(property, out pi))
            {
                return new ArgumentException(SR.UnsupportedProperty);
            }
 
            object value;
            // PRESHARP will flag this as warning 56506/6506:Parameter 'property' to this public method must be validated: A null-dereference can occur here.
            // False positive, property is checked, see above
#pragma warning suppress 6506
             UiaCoreApi.UiaGetPropertyValue(_hnode, property.Id, out value);
            if (value != AutomationElement.NotSupported)
            {
                // we need to verify that we've got the expected basic variant type before casting/returning?
                // We've got a variant - but that collapses all enums to ints, for example.
                // Convert back to a more appropriate managed type if necessary...
                if (value != null && pi.ObjectConverter != null)
                {
                    value = pi.ObjectConverter(value);
                }
            }
            else
            {
                if (ignoreDefaultValue)
                {
                    value = AutomationElement.NotSupported;
                }
                else
                {
                    value = Schema.GetDefaultValue(property);
                }
            }
 
 
            return value;
        }
 
        /// <summary>
        /// Get a pattern class from this object
        /// </summary>
        /// <param name="pattern">AutomationPattern indicating the pattern to return</param>
        /// <returns>Returns the pattern as an object, if currently supported</returns>
        /// <remarks>
        /// Throws InvalidOperationException if the pattern is not supported.
        /// 
        /// This API gets the pattern based on availability at this point in time,
        /// without checking the cache. For some types of UI, this API will incur
        /// a cross-process performance hit. To access patterns in this AutomationElement's
        /// cache, use GetCachedPattern instead.
        /// </remarks>
        public object GetCurrentPattern(AutomationPattern pattern)
        {
            object retObject;
            if (!TryGetCurrentPattern(pattern, out retObject))
            {
                throw new InvalidOperationException(SR.UnsupportedPattern);
            }
 
            return retObject;
        }
 
 
        /// <summary>
        /// Get a pattern class from this object
        /// </summary>
        /// <param name="pattern">an object repressenting the AutomationPattern indicating
        /// the pattern to return</param>
        /// <param name="patternObject">the returned pattern object will be an object 
        /// implementing the control pattern interface if the pattern is supported else 
        /// the object will be null.</param>
        /// <returns>Returns true, if currently supported</returns>
        /// <remarks>
        /// This API gets the pattern based on availability at this point in time,
        /// without checking the cache. For some types of UI, this API will incur
        /// a cross-process performance hit. To access patterns in this AutomationElement's
        /// cache, use GetCachedPattern instead.
        /// </remarks>
        public bool TryGetCurrentPattern(AutomationPattern pattern, out object patternObject)
        {
            patternObject = null;
            ArgumentNullException.ThrowIfNull(pattern);
            CheckElement();
            // Need to catch non-critical exceptions. The WinFormsSpinner will raise an
            // InvalidOperationException if it is a domain spinner and the SelectionPattern is asked for.
            SafePatternHandle hpatternobj = null;
            try
            {
                hpatternobj = UiaCoreApi.UiaGetPatternProvider(_hnode, pattern.Id);
            }
            catch (Exception e)
            {
                if (Misc.IsCriticalException(e))
                {
                    throw;
                }
                return false;
            }
            if (hpatternobj.IsInvalid)
            {
                return false;
            }
 
            patternObject = Misc.WrapInterfaceOnClientSide(this, hpatternobj, pattern);
            return patternObject != null;
        }
 
 
        /// <summary>
        /// Get cached value of specified property from an element.
        /// </summary>
        /// <param name="property">AutomationProperty that identifies the property</param>
        /// <returns>Returns value of specified property</returns>
        /// <remarks>
        /// Throws InvalidOperationException if the requested property was not
        /// previously specified to be pre-fetched using a CacheRequest.
        /// 
        /// If the specified property is not explicitly supported by the target UI,
        /// a default value will be returned. For example, if the target UI doesn't
        /// support the AutomationElement.NameProperty, calling GetCachedPropertyValue
        /// for that property will return an empty string.
        /// </remarks>
        public object GetCachedPropertyValue(AutomationProperty property)
        {
            return GetCachedPropertyValue(property, false);
        }
 
        /// <summary>
        /// Get the cached value of specified property from an element.
        /// </summary>
        /// <param name="property">AutomationProperty that identifies the property</param>
        /// <param name="ignoreDefaultValue">Specifies whether a default value should be
        /// ignored if the specified property is not supported by the target UI</param>
        /// <returns>Returns value of specified property, or AutomationElement.NotSupported</returns>
        /// <remarks>
        /// Throws InvalidOperationException if the requested property was not
        /// previously specified to be pre-fetched using a CacheRequest.
        /// 
        /// If ignoreDefaultValue is true, then when the specified property is not
        /// explicitly supported by the target UI, a default value will not be returned.
        /// For example, if the target UI doesn't
        /// support the AutomationElement.NameProperty, calling GetCachedPropertyValue
        /// for that property will return an empty string.
        /// When ignoreDefaultValue is true, the value AutomationElement.NotSupported will
        /// be returned instead.
        /// </remarks>
        public object GetCachedPropertyValue(AutomationProperty property, bool ignoreDefaultValue)
        {
            ArgumentNullException.ThrowIfNull(property);
 
            // true -> throw if not available, true -> wrap
            object val = LookupCachedValue(property, true, true);
 
            UiaCoreApi.IsErrorMarker(val, true/*throwException*/);
 
            if (val == AutomationElement.NotSupported && !ignoreDefaultValue)
            {
                val = Schema.GetDefaultValue(property);
            }
 
            return val;
        }
 
        /// <summary>
        /// Get a pattern class from this object
        /// </summary>
        /// <param name="pattern">AutomationPattern indicating the pattern to return</param>
        /// <returns>Returns the pattern as an object, if currently supported; otherwise returns null/</returns>
        /// <remarks>
        /// Throws InvalidOperationException if the requested pattern was not
        /// previously specified to be pre-fetched using a CacheRequest.
        /// 
        /// This API gets the pattern from the cache. 
        /// </remarks>
        public object GetCachedPattern(AutomationPattern pattern)
        {
            object patternObject;
            if (!TryGetCachedPattern(pattern, out patternObject))
            {
                throw new InvalidOperationException(SR.UnsupportedPattern);
            }
            return patternObject;
        }
 
        /// <summary>
        /// Get a pattern class from this object
        /// </summary>
        /// <param name="pattern">AutomationPattern indicating the pattern to return</param>
        /// <param name="patternObject">Is the pattern as an object, if currently in the cache; otherwise is null</param>
        /// <returns>Returns true, if currently in the cache; otherwise returns false</returns>
        /// <remarks>
        /// This API gets the pattern from the cache. 
        /// </remarks>
        public bool TryGetCachedPattern(AutomationPattern pattern, out object patternObject)
        {
            patternObject = null;
 
            // Lookup a cached remote reference - but even if we get
            // back null, still go ahead an create a pattern wrapper
            // to provide access to cached properties
            ArgumentNullException.ThrowIfNull(pattern);
 
            // false -> don't throw, false -> don't wrap
            object obj = LookupCachedValue(pattern, false, false);
            if (obj == null)
            {
                return false;
            }
            SafePatternHandle hPattern = (SafePatternHandle)obj;
 
            AutomationPatternInfo pi;
            if (!Schema.GetPatternInfo(pattern, out pi))
            {
                throw new ArgumentException(SR.UnsupportedPattern);
            }
 
            patternObject = pi.ClientSideWrapper(this, hPattern, true);
 
            return patternObject != null;
        }
 
        /// <summary>
        /// Get an AutomationElement with updated cached values
        /// </summary>
        /// <param name="request">CacheRequest object describing the properties and other information to fetch</param>
        /// <returns>Returns a new AutomationElement, which refers to the same UI as this element, but which is
        /// populated with properties specified in the CacheRequest.</returns>
        /// <remarks>
        /// Unlike other methods, such as FromHandle, FromPoint, this method takes
        /// an explicit CacheRequest as a parameter, and ignores the currently
        /// active CacheRequest.
        /// </remarks>
        public AutomationElement GetUpdatedCache(CacheRequest request)
        {
            ArgumentNullException.ThrowIfNull(request);
            CheckElement();
 
            UiaCoreApi.UiaCacheRequest cacheRequest = request.GetUiaCacheRequest();
 
            // Don't normalize when getting updated cache...
            UiaCoreApi.UiaCacheResponse response = UiaCoreApi.UiaGetUpdatedCache(_hnode, cacheRequest, UiaCoreApi.NormalizeState.None, null);
            return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
        }
 
        /// <summary>
        /// Find first child or descendant element that matches specified condition
        /// </summary>
        /// <param name="scope">Indicates whether to include this element, children
        /// or descendants in the search</param>
        /// <param name="condition">Condition to search for</param>
        /// <returns>Returns first element that satisfies condition,
        /// or null if no match is found.</returns>
        public AutomationElement FindFirst(TreeScope scope, Condition condition)
        {
            ArgumentNullException.ThrowIfNull(condition);
            UiaCoreApi.UiaCacheResponse[] responses = Find(scope, condition, CacheRequest.CurrentUiaCacheRequest, true, null);
            if (responses.Length < 1)
            {
                return null;
            }
 
            Debug.Assert(responses.Length == 1);
 
            return CacheHelper.BuildAutomationElementsFromResponse(CacheRequest.CurrentUiaCacheRequest, responses[0]);
        }
 
        /// <summary>
        /// Find all child or descendant elements that match specified condition
        /// </summary>
        /// <param name="scope">Indicates whether to include this element, children
        /// or descendants in the search</param>
        /// <param name="condition">Condition to search for</param>
        /// <returns>Returns collection of all AutomationElements that
        /// match specified condition. Collection will be empty if
        /// no matches found.</returns>
        public AutomationElementCollection FindAll(TreeScope scope, Condition condition)
        {
            ArgumentNullException.ThrowIfNull(condition);
            UiaCoreApi.UiaCacheRequest request = CacheRequest.CurrentUiaCacheRequest;
            UiaCoreApi.UiaCacheResponse[] responses = Find(scope, condition, request, false, null);
 
            AutomationElement[] els = new AutomationElement[responses.Length];
 
            for( int i = 0 ; i < responses.Length ; i ++ )
            {
                els[i] = CacheHelper.BuildAutomationElementsFromResponse(request, responses[i]);
            }
 
            return new AutomationElementCollection( els );
        }
 
        /// <summary>
        /// Get array of supported property identifiers
        /// </summary>
        /// <remarks>
        /// The returned array contains at least all the properties supported by this element;
        /// however it may also contain duplicate entries or properties that the element does not
        /// currently support or which have null or empty values. Use GetPropertyValue to determine
        /// whether a property is currently supported and to determine what its current value is.
        /// </remarks>
        public AutomationProperty [ ] GetSupportedProperties()
        {
            CheckElement();
 
            ArrayList propArrays = new ArrayList(4);
            propArrays.Add(Schema.GetBasicProperties());
 
            AutomationPattern[] patterns = GetSupportedPatterns();
            if (patterns != null && patterns.Length > 0)
            {
                foreach (AutomationPattern pattern in patterns)
                {
                    AutomationPatternInfo pi;
                    if (Schema.GetPatternInfo(pattern, out pi))
                    {
                        if (pi.Properties != null)
                        {
                            propArrays.Add(pi.Properties);
                        }
                    }
                }
            }
 
            return (AutomationProperty[])Misc.RemoveDuplicates(Misc.CombineArrays(propArrays, typeof(AutomationProperty)), typeof(AutomationProperty));
        }
 
        /// <summary>
        /// Get the interfaces that this object supports
        /// </summary>
        /// <returns>An array of AutomationPatterns that represent the supported interfaces</returns>
        public AutomationPattern [ ] GetSupportedPatterns()
        {
            CheckElement();
 
            ArrayList interfaces = new ArrayList(4);
            object patternObject;
            foreach (AutomationPatternInfo pi in Schema.GetPatternInfoTable())
            {
                if (pi.ID != null && TryGetCurrentPattern(pi.ID, out patternObject))
                {
                    interfaces.Add(pi.ID);
                }
            }
 
            return (AutomationPattern[])interfaces.ToArray(typeof(AutomationPattern));
        }
 
        /// <summary>
        /// Request to set focus to this element
        /// </summary>
        public void SetFocus()
        {
            CheckElement();
 
            object canReceiveFocus = GetCurrentPropertyValue(AutomationElement.IsKeyboardFocusableProperty);
 
            if (canReceiveFocus is bool && (bool)canReceiveFocus)
            {
                UiaCoreApi.UiaSetFocus(_hnode);
            }
            else
            {
                throw new InvalidOperationException(SR.SetFocusFailed);
            }
        }
 
        /// <summary>
        /// Get a point that can be clicked on.  If there is no ClickablePoint return false
        /// </summary>
        /// <param name="pt">A point that can be used ba a client to click on this LogicalElement</param>
        /// <returns>true if there is point that is clickable</returns>
        public bool TryGetClickablePoint( out Point pt )
        {
            // initialize point here so if we return false its initialized
            pt = new Point (0, 0);
 
            // Request the provider for a clickable point. 
            object ptClickable = GetCurrentPropertyValue(AutomationElement.ClickablePointProperty);
 
            if (ptClickable == NotSupported)
            {
                return false;
            }
 
            // if got one
            if (ptClickable is Point)
            {
                //If the ClickablePointProperty from the provider is NaN that means no point.
                if (double.IsNaN (((Point) ptClickable).X) || double.IsNaN (((Point) ptClickable).Y))
                {
                    return false;
                }
 
                // Allow the object if it is the element or a descentant...
                AutomationElement scan = AutomationElement.FromPoint((Point)ptClickable);
                while (scan != null)
                {
                    if (scan == this)
                    {
                        pt = (Point)ptClickable;
                        return true;
                    }
 
                    scan = TreeWalker.RawViewWalker.GetParent(scan, CacheRequest.DefaultCacheRequest);
                }
            }
            
            // the providers point is either no good or they did not have one so poke around 
            // trying to find one.
            if (ClickablePoint.HitTestForClickablePoint( (AutomationElement)this, out pt) )
                return true;
            
            return false;
        }
        
        /// <summary>
        /// Get a point that can be clicked on.  This throws the NoClickablePointException if there is no clickable point
        /// </summary>
        /// <returns>A point that can be used by a client to click on this LogicalElement</returns>
        /// <exception cref="NoClickablePointException">If there is not clickable point for this element</exception>
        public Point GetClickablePoint()
        {
            Point pt;
            if ( !TryGetClickablePoint( out pt ) )
                throw new NoClickablePointException(SR.LogicalElementNoClickablePoint);
 
            return pt;
        }
        #endregion Public Methods
 
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Get root element for current desktop
        /// </summary>
        /// <returns>root element for current desktop</returns>
        public static AutomationElement RootElement
        {
            get
            {
                SafeNodeHandle hnode = UiaCoreApi.UiaGetRootNode();
 
                UiaCoreApi.UiaCacheRequest cacheRequest = CacheRequest.CurrentUiaCacheRequest;
 
                // Don't normalize...
                UiaCoreApi.UiaCacheResponse response = UiaCoreApi.UiaGetUpdatedCache(hnode, cacheRequest, UiaCoreApi.NormalizeState.None, null);
                // do we need to release hnode from above?
 
                return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
            }
        }
 
        /// <summary>
        /// Return the currently focused element
        /// </summary>
        public static AutomationElement FocusedElement
        {
            get
            {
                return DrillForPointOrFocus(false, new Point(0, 0), CacheRequest.CurrentUiaCacheRequest);
            }
        }
 
        /// <summary>
        /// This member allows access to previously requested
        /// cached properties for this element. The returned object
        /// has accessors for AutomationElement properties.
        /// </summary>
        /// <remarks>
        /// Cached property values must have been previously requested
        /// using a CacheRequest. If you try to access a cached
        /// property that was not previously requested, an InvalidOperation
        /// Exception will be thrown.
        /// 
        /// To get the value of a property at the current point in time,
        /// access the property via the Current accessor instead of
        /// Cached.
        /// </remarks>
        public AutomationElementInformation Cached
        {
            get
            {
                return new AutomationElementInformation(this, true);
            }
        }
 
        /// <summary>
        /// This member allows access to current property values
        /// for this element. The returned object has accessors for
        /// AutomationElement properties.
        /// </summary>
        /// <remarks>
        /// This AutomationElement must have a
        /// Full reference in order to get current values. If the
        /// AutomationElement was obtained using AutomationElementMode.None,
        /// then it contains only cached data, and attempting to get
        /// the current value of any property will throw an InvalidOperationException.
        /// 
        /// To get the cached value of a property that was previously
        /// specified using a CacheRequest, access the property via the
        /// Cached accessor instead of Current.
        /// </remarks>
        public AutomationElementInformation Current
        {
            get
            {
                return new AutomationElementInformation(this, false);
            }
        }
 
        /// <summary>
        /// Returns the cached parent of this AutomationElement
        /// </summary>
        /// <remarks>
        /// Returns the parent of this element, with respect to the TreeFilter
        /// condition of the CacheRequest that was active when this AutomationElement
        /// was obtained.
        /// 
        /// Throws InvalidOperationException if the parent was not previously requested
        /// in a CacheRequest.
        /// 
        /// Can return null if the specified element has no parent - eg. is the root node.
        /// </remarks>
        public AutomationElement CachedParent
        {
            get
            {
                // this is used as a marker to indicate 'not requested'
                // - used since null is a valid value for parent, but this can never be.
                // Use (object) case to ensure we just do a ref check here, not call .Equals
                if ((object)_cachedParent == (object)this)
                {
                    // PRESHARP will flag this as a warning 56503/6503: Property get methods should not throw exceptions
                    // We've spec'd as throwing an Exception, and that's what we do PreSharp shouldn't complain
#pragma warning suppress 6503
                    throw new InvalidOperationException(SR.CachedPropertyNotRequested);
                }
 
                return _cachedParent;
            }
        }
 
        /// <summary>
        /// Returns the cached children of this AutomationElement
        /// </summary>
        /// <remarks>
        /// Returns a collection of children of this element, with respect to the TreeFilter
        /// condition of the CacheRequest that was active when this AutomationElement
        /// was obtained.
        /// 
        /// Throws InvalidOperationException if children or descendants were not previously requested
        /// in a CacheRequest.
        /// 
        /// Can return an empty collection if this AutomationElement has no children.
        /// </remarks>
        public AutomationElementCollection CachedChildren
        {
            get
            {
                // this is used as a marker to indicate 'not requested'
                // - used since null is a valid value for parent, but this can never be.
                // Use (object) case to ensure we just do a ref check here, not call .Equals
                if ((object)_cachedFirstChild == (object)this)
                {
                    // PRESHARP will flag this as a warning 56503/6503: Property get methods should not throw exceptions
                    // We've spec'd as throwing an Exception, and that's what we do PreSharp shouldn't complain
#pragma warning suppress 6503
                    throw new InvalidOperationException(SR.CachedPropertyNotRequested);
                }
 
                // Build up an array to return - first count the children,
                // then build an array and populate it...
                int childCount = 0;
                AutomationElement scan = _cachedFirstChild;
 
                for (; scan != null; scan = scan._cachedNextSibling)
                {
                    childCount++;
                }
 
                AutomationElement[] children = new AutomationElement[childCount];
 
                scan = _cachedFirstChild;
                for (int i = 0; i < childCount; i++)
                {
                    children[i] = scan;
                    scan = scan._cachedNextSibling;
                }
 
                return new AutomationElementCollection(children);
            }
        }
 
 
        #endregion Public Properties
 
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        internal void CheckElement()
        {
            if (_hnode == null || _hnode.IsInvalid)
            {
                throw new InvalidOperationException(SR.CacheRequestNeedElementReference);
            }
        }
 
        // Called by the treewalker classes to navigate - we call through to the
        // provider wrapper, which gets the navigator code to do its stuff
        internal AutomationElement Navigate(NavigateDirection direction, Condition condition, CacheRequest request)
        {
            CheckElement();
 
            UiaCoreApi.UiaCacheRequest cacheRequest;
            if (request == null)
                cacheRequest = CacheRequest.DefaultUiaCacheRequest;
            else
                cacheRequest = request.GetUiaCacheRequest();
 
            UiaCoreApi.UiaCacheResponse response = UiaCoreApi.UiaNavigate(_hnode, direction, condition, cacheRequest);
            return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
        }
 
        internal AutomationElement Normalize(Condition condition, CacheRequest request )
        {
            CheckElement();
 
            UiaCoreApi.UiaCacheRequest cacheRequest;
            if (request == null)
                cacheRequest = CacheRequest.DefaultUiaCacheRequest;
            else
                cacheRequest = request.GetUiaCacheRequest();
 
            // Normalize against the treeview condition, not the one in the cache request...
            UiaCoreApi.UiaCacheResponse response = UiaCoreApi.UiaGetUpdatedCache(_hnode, cacheRequest, UiaCoreApi.NormalizeState.Custom, condition);
            return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
        }
 
 
        // Used by the pattern wrappers to get property values
        internal object GetPatternPropertyValue(AutomationProperty property, bool useCache)
        {
            if (useCache)
                return GetCachedPropertyValue(property);
            else
                return GetCurrentPropertyValue(property);
        }
 
 
        // The following are used by CacheUtil when building up a cached AutomationElemen tree
 
        internal void SetCachedParent(AutomationElement cachedParent)
        {
            _cachedParent = cachedParent;
            // If we're setting the parent, it means this is one of potentially
            // many siblings - so set _cachedNextSibling to null, instead of
            // the 'not requested' marker value 'this'
            _cachedNextSibling = null;
        }
 
        internal void SetCachedFirstChild(AutomationElement cachedFirstChild)
        {
            _cachedFirstChild = cachedFirstChild;
        }
 
        internal void SetCachedNextSibling(AutomationElement cachedNextSibling)
        {
            _cachedNextSibling = cachedNextSibling;
        }
 
        #endregion Internal Methods
 
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        internal SafeNodeHandle RawNode
        {
            get
            {
                return _hnode;
            }
        }
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // Lookup a cached AutomationPattern or AutomationProperty
        object LookupCachedValue(AutomationIdentifier id, bool throwIfNotRequested, bool wrap)
        {
            if (_cachedValues == null)
            {
                if (throwIfNotRequested)
                {
                    throw new InvalidOperationException(SR.CachedPropertyNotRequested);
                }
                else
                {
                    return null;
                }
            }
 
            AutomationProperty automationProperty = id as AutomationProperty;
 
            bool isProperty = automationProperty != null;
            AutomationIdentifier[] refTable = isProperty ? (AutomationIdentifier[])_request.Properties
                                                           : (AutomationIdentifier[])_request.Patterns;
            bool found = false;
            object val = null;
 
            int dataOffset = isProperty ? 1 : 1 + _request.Properties.Length;
            for (int i = 0; i < refTable.Length; i++)
            {
                if (refTable[i] == id)
                {
                    found = true;
                    val = _cachedValues[_cachedValuesIndex, i + dataOffset];
                    break;
                }
            }
 
            if (!found)
            {
                if (throwIfNotRequested)
                {
                    throw new InvalidOperationException(SR.CachedPropertyNotRequested);
                }
                else
                {
                    return null;
                }
            }
 
            // Bail now if no wrapping required; also, even with wrapping, null remains null
            // for both properties and patterns..
            if (!wrap || val == null)
            {
                return val;
            }
 
            AutomationPattern automationPattern = id as AutomationPattern;
 
            // Cached values are internally stored as unwrapped, direct-from-provider values, so
            // need to be wrapped as appropriate before handing back to client...
            if (automationPattern != null)
            {
                SafePatternHandle hpatternobj = (SafePatternHandle)val;
                val = Misc.WrapInterfaceOnClientSide(this, hpatternobj, automationPattern);
            }
 
            // No wrapping necessary here for properties - the objects in the array are fully wrapped/converted as soon as they are
            // received from the unmanaged API, so they're ready-to-use without any further processing.
            return val;
        }
 
        // drill for either focused raw element, or element at specified point
        private static AutomationElement DrillForPointOrFocus(bool atPoint, Point pt, UiaCoreApi.UiaCacheRequest cacheRequest)
        {
            UiaCoreApi.UiaCacheResponse response;
            if (atPoint)
                response = UiaCoreApi.UiaNodeFromPoint(pt.X, pt.Y, cacheRequest);
            else
                response = UiaCoreApi.UiaNodeFromFocus(cacheRequest);
 
            return CacheHelper.BuildAutomationElementsFromResponse(cacheRequest, response);
        }
 
 
        // called by FindFirst and FindAll
        private UiaCoreApi.UiaCacheResponse[] Find(TreeScope scope, Condition condition, UiaCoreApi.UiaCacheRequest request, bool findFirst, BackgroundWorker worker)
        {
            ArgumentNullException.ThrowIfNull(condition);
            if (scope == 0)
            {
                throw new ArgumentException(SR.TreeScopeNeedAtLeastOne);
            }
            if ((scope & ~(TreeScope.Element | TreeScope.Children | TreeScope.Descendants)) != 0)
            {
                throw new ArgumentException(SR.TreeScopeElementChildrenDescendantsOnly);
            }
 
            // Set up a find struct...
            UiaCoreApi.UiaFindParams findParams = new UiaCoreApi.UiaFindParams();
            findParams.FindFirst = findFirst;
 
            if ((scope & TreeScope.Descendants) != 0)
                findParams.MaxDepth = -1;
            else if ((scope & TreeScope.Children) != 0)
                findParams.MaxDepth = 1;
            else
                findParams.MaxDepth = 0;
 
            if ((scope & TreeScope.Element) != 0)
                findParams.ExcludeRoot = false;
            else
                findParams.ExcludeRoot = true;
 
            UiaCoreApi.UiaCacheResponse[] retVal = UiaCoreApi.UiaFind(_hnode, findParams, condition, request);
            return retVal;
        }
        #endregion Private Methods
 
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        private SafeNodeHandle _hnode;
        private int[] _runtimeId;
 
        // Cached object values - use in conjunction with the Properties/Pattern arrays in
        // _request to figure out which properties/patterns they are.
        // Note that these use NotSupported, so need to substitute default values
        // when returning to user.
        private object[,] _cachedValues;
        private int _cachedValuesIndex; // index of row in cachedValues that corresponds to this element
 
        // Reference to the cache request information that was active when this
        // element was created
        private UiaCoreApi.UiaCacheRequest _request;
 
        // Cached structure links - these set to 'this' to indicate that they
        // were not requested - since null is a valid value.
        private AutomationElement _cachedParent;
        private AutomationElement _cachedFirstChild;
        private AutomationElement _cachedNextSibling;
 
        #endregion Private Fields
 
        //------------------------------------------------------
        //
        //  Nested Classes
        //
        //------------------------------------------------------
 
        #region Nested Classes
 
        /// <summary>
        /// This class provides access to either Cached or Current
        /// properties on an AutomationElement via the .Cached or
        /// .Current accessors.
        /// </summary>
        public struct AutomationElementInformation
        {
            //------------------------------------------------------
            //
            //  Constructors
            //
            //------------------------------------------------------
 
            #region Constructors
 
            internal AutomationElementInformation(AutomationElement el, bool useCache)
            {
                _el = el;
                _useCache = useCache;
            }
 
            #endregion Constructors
 
 
            //------------------------------------------------------
            //
            //  Public Properties
            //
            //------------------------------------------------------
 
            #region Public Properties
 
            /// <summary>The ControlType of this Element</summary>
            public ControlType  ControlType           { get { return (ControlType) _el.GetPatternPropertyValue(ControlTypeProperty,          _useCache); } }
 
            /// <summary>Localized control type description (eg. "Button")</summary>
            public string       LocalizedControlType  { get { return (string)      _el.GetPatternPropertyValue(LocalizedControlTypeProperty, _useCache); } }
            
            /// <summary>Name of this instance of control</summary>
            public string       Name                  { get { return (string)      _el.GetPatternPropertyValue(NameProperty,                 _useCache); } }
            
            /// <summary>Hot-key equivalent for this command item. (eg. Ctrl-P for Print)</summary>
            public string       AcceleratorKey        { get { return (string)      _el.GetPatternPropertyValue(AcceleratorKeyProperty,       _useCache); } }
            
            /// <summary>Keys used to move focus to this control</summary>
            public string       AccessKey             { get { return (string)      _el.GetPatternPropertyValue(AccessKeyProperty,            _useCache); } }
            
            /// <summary>Indicates whether this control has keyboard focus</summary>
            public bool         HasKeyboardFocus      { get { return (bool)        _el.GetPatternPropertyValue(HasKeyboardFocusProperty,     _useCache); } }
            
            /// <summary>True if this control can take keyboard focus</summary>
            public bool         IsKeyboardFocusable   { get { return (bool)        _el.GetPatternPropertyValue(IsKeyboardFocusableProperty,  _useCache); } }
            
            /// <summary>True if this control is enabled</summary>
            public bool         IsEnabled             { get { return (bool)        _el.GetPatternPropertyValue(IsEnabledProperty,            _useCache); } }
            
            /// <summary>Bounding rectangle, in screen coordinates</summary>
            public Rect         BoundingRectangle     { get { return (Rect)        _el.GetPatternPropertyValue(BoundingRectangleProperty,    _useCache); } }
            
            /// <summary>HelpText - brief description of what this control does</summary>
            public string       HelpText              { get { return (string)      _el.GetPatternPropertyValue(HelpTextProperty,             _useCache); } }
            
            /// <summary>Indicates that this element should be included in the Control view of the tree</summary>
            public bool         IsControlElement      { get { return (bool)        _el.GetPatternPropertyValue(IsControlElementProperty,     _useCache); } }
 
            /// <summary>Indicates that this element should be included in the Content view of the tree</summary>
            public bool         IsContentElement      { get { return (bool)        _el.GetPatternPropertyValue(IsContentElementProperty,     _useCache); } }
 
            /// <summary>The AutomationElement that labels this element</summary>
            public AutomationElement LabeledBy        { get { return (AutomationElement) _el.GetPatternPropertyValue(LabeledByProperty,      _useCache); } }
 
            /// <summary>The identifier for an element that is unique within its containing element</summary>
            public string       AutomationId          { get { return (string)      _el.GetPatternPropertyValue(AutomationIdProperty,         _useCache); } }
 
            /// <summary>Localized string that indicates what the items in a list represent</summary>
            public string       ItemType              { get { return (string)      _el.GetPatternPropertyValue(ItemTypeProperty,             _useCache ); } }
 
            /// <summary>True if the control is a password protected field.</summary>
            public bool         IsPassword            { get { return (bool)        _el.GetPatternPropertyValue(IsPasswordProperty,           _useCache); } }
 
            /// <summary>Name of underlying class - implementation dependant, but useful for test</summary>
            public string       ClassName             { get { return (string)      _el.GetPatternPropertyValue(ClassNameProperty,            _useCache); } }
 
            /// <summary>Window Handle, if the underlying control is a Window</summary>
            public int          NativeWindowHandle    { get { return (int)         _el.GetPatternPropertyValue(NativeWindowHandleProperty,   _useCache); } }
 
            /// <summary>Id of process that this element lives in</summary>
            public int          ProcessId             { get { return (int)         _el.GetPatternPropertyValue(ProcessIdProperty,            _useCache); } }
            
            /// <summary>True if this control is not visible to the sighted user</summary>
            public bool         IsOffscreen           { get { return (bool)        _el.GetPatternPropertyValue(IsOffscreenProperty,          _useCache); } }
 
            /// <summary>The controls specfied direction</summary>
            public OrientationType Orientation        { get { return (OrientationType) _el.GetPatternPropertyValue(OrientationProperty,      _useCache); } }
 
            /// <summary>The controls specfied direction</summary>
            public string       FrameworkId           { get { return (string)      _el.GetPatternPropertyValue(FrameworkIdProperty,          _useCache); } }
 
            /// <summary>True if this element is required to be filled out on a form</summary>
            public bool         IsRequiredForForm     { get { return (bool)        _el.GetPatternPropertyValue(IsRequiredForFormProperty,    _useCache); } }
 
            /// <summary>The visual status of a complex item as a string</summary>
            public string       ItemStatus            { get { return (string)      _el.GetPatternPropertyValue(ItemStatusProperty,           _useCache); } }
 
            #endregion Public Properties
 
            //------------------------------------------------------
            //
            //  Private Fields
            //
            //------------------------------------------------------
 
            #region Private Fields
 
            private AutomationElement _el; // AutomationElement that contains the cache or live reference
 
            private bool _useCache; // true to use cache, false to use live reference to get current values
 
            #endregion Private Fields
        }
        #endregion Nested Classes
    }
}