File: MS\Internal\Automation\ElementProxy.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// 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: UIAutomation/Visual bridge
//
//
 
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
 
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Peers;
using System.Diagnostics;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
 
using System.Security;
 
using MS.Internal.PresentationCore;
using MS.Win32;
using SR=MS.Internal.PresentationCore.SR;
 
namespace MS.Internal.Automation
{
    // Automation Proxy for WCP elements - exposes WCP tree
    // and properties to Automation.
    //
    // Currently builds tree based on Visual tree; many later incorporate
    // parts of logical tree.
    //
    // Currently exposes just BoundingRectangle, ClassName, IsEnabled
    // and IsKeyboardFocused properties.
    internal class ElementProxy: IRawElementProviderFragmentRoot, IRawElementProviderAdviseEvents
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // private ctor - the Wrap() pseudo-ctor is used instead.
        private ElementProxy(AutomationPeer peer)
        {
            if ((AutomationInteropReferenceType == ReferenceType.Weak) && 
                (peer is UIElementAutomationPeer || peer is ContentElementAutomationPeer || peer is UIElement3DAutomationPeer))
            {
                _peer = new WeakReference(peer);
            }
            else
            {
                _peer = peer;
            }
        }
 
        #endregion Constructors
 
        //------------------------------------------------------
        //
        //  Interface IRawElementProviderFragmentRoot
        //
        //------------	------------------------------------------
 
        #region Interface IRawElementProviderFragmentRoot
        // Implementation of the interface that exposes this
        // to UIAutomation.
        //
        // Interface IRawElementProviderFragmentRoot 'derives' from
        // IRawElementProviderSimple and IRawElementProviderFragment,
        // methods from those appear first below.
        //
        // Most of the interface methods on this interface need
        // to get onto the Visual's context before they can access it,
        // so use Dispatcher.Invoke to call a private method (in the
        // private methods section of this class) that does the real work.
 
        // IRawElementProviderSimple methods...
 
        public object GetPatternProvider ( int pattern )
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return ElementUtil.Invoke(peer, new DispatcherOperationCallback( InContextGetPatternProvider ), pattern);
        }
 
        public object GetPropertyValue(int property)
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return ElementUtil.Invoke(peer, new DispatcherOperationCallback(InContextGetPropertyValue), property);
        }
 
        public ProviderOptions ProviderOptions
        {
            get 
            {
                AutomationPeer peer = Peer;
                if (peer == null)
                {
                    return ProviderOptions.ServerSideProvider;
                }
                return (ProviderOptions)ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextGetProviderOptions(), this); 
            }
        }  
 
        public IRawElementProviderSimple HostRawElementProvider
        {
            get
            {
                IRawElementProviderSimple host  = null;
                HostedWindowWrapper hwndWrapper = null;
                AutomationPeer peer = Peer;
                if (peer == null)
                {
                    return null;
                }
                hwndWrapper = (HostedWindowWrapper)ElementUtil.Invoke(
                    peer, 
                    new DispatcherOperationCallback(InContextGetHostRawElementProvider), 
                    null);
 
                if(hwndWrapper != null)
                    host = GetHostHelper(hwndWrapper);
                
                return host;
            }
        }
 
        private IRawElementProviderSimple GetHostHelper(HostedWindowWrapper hwndWrapper)
        {
            return AutomationInteropProvider.HostProviderFromHandle(hwndWrapper.Handle);
        }
 
        // IRawElementProviderFragment methods...
 
        public IRawElementProviderFragment Navigate( NavigateDirection direction )
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            return (IRawElementProviderFragment)ElementUtil.Invoke(peer, new DispatcherOperationCallback(InContextNavigate), direction);
        }
 
        public int [ ] GetRuntimeId()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return (int []) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextGetRuntimeId(), this);
        }
 
        public Rect BoundingRectangle
        {
            get 
            {
                AutomationPeer peer = Peer;
                if (peer == null)
                {
                    throw new ElementNotAvailableException();
                }
                return (Rect)ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextBoundingRectangle(), this); 
            }
        }
        
        public IRawElementProviderSimple [] GetEmbeddedFragmentRoots()
        {
            return null;
        }
        
        public void SetFocus()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextSetFocus(), this);
        }
 
        public IRawElementProviderFragmentRoot FragmentRoot
        {
            get 
            {
                AutomationPeer peer = Peer;
                if (peer == null)
                {
                    return null;
                }
                return (IRawElementProviderFragmentRoot) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextFragmentRoot(), this); 
            }
        }
 
        // IRawElementProviderFragmentRoot methods..
        public IRawElementProviderFragment ElementProviderFromPoint( double x, double y )
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            return (IRawElementProviderFragment) ElementUtil.Invoke( peer, new DispatcherOperationCallback( InContextElementProviderFromPoint ), new Point( x, y ) );
        }
 
        public IRawElementProviderFragment GetFocus()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            return (IRawElementProviderFragment) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextGetFocus(), this);
        }
 
        // Event support: EventMap is a static class and access is synchronized, so no need to access it in UI thread context.
        // Directly add or remove the requested EventId from the EventMap from here.
        // Note: We update the  EventMap even if peer is not available that's because we still have to keep track of number of
        // subscribers  for the given event.
 
        public void AdviseEventAdded(int eventID, int[] properties)
        {
            EventMap.AddEvent(eventID);
        }
 
        public void AdviseEventRemoved(int eventID, int[] properties)
        {
            EventMap.RemoveEvent(eventID);
        }
 
        #endregion Interface IRawElementProviderFragmentRoot
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // Wrap the found peer before shipping it over process boundary - static version for use outside ElementProxy.
        internal static ElementProxy StaticWrap(AutomationPeer peer, AutomationPeer referencePeer)
        {
            ElementProxy result = null;
 
            if (peer != null)
            {
                //referencePeer is well-connected, since UIA is asking us using it.
                //However we are trying to return the peer that is possibly just created on some 
                //element in the middle of un-walked tree and we need to make sure it is fully
                //"connected" or initialized before we return it to UIA.
                //This method ensures it has the right _parent and other hookup.
                peer = peer.ValidateConnected(referencePeer);
                if (peer != null)
                {
                    // Use the already created Wrapper proxy for peer if it is still alive
                    if(peer.ElementProxyWeakReference != null)
                    {
                        result = peer.ElementProxyWeakReference.Target as ElementProxy;
                    }
                    if(result == null)
                    {
                        result = new ElementProxy(peer);
                        peer.ElementProxyWeakReference = new WeakReference(result);
                    }
 
                    // If the peer corresponds to DataItem notify peer to add to storage to 
                    // keep reference of peers going to the UIA Client
                    if(result != null)
                    {
                        if(peer.IsDataItemAutomationPeer())
                        {
                            peer.AddToParentProxyWeakRefCache();
                        }
                    }
                }
            }
 
            return result;
        }
 
 
        // Needed to enable access from AutomationPeer.PeerFromProvider
        internal AutomationPeer Peer
        {
            get
            {
                if (_peer is WeakReference)
                {
                    AutomationPeer peer = (AutomationPeer)((WeakReference)_peer).Target;
                    return peer;
                }
                else
                {
                    return (AutomationPeer)_peer;
                }
            }
        }
 
        #endregion Internal Methods
 
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // The signature of most of the folling methods is "object func( object arg )",
        // since that's what the Conmtext.Invoke delegate requires.
        // Return the element at specified coords.
        private object InContextElementProviderFromPoint( object arg )
        {
            Point point = (Point)arg;
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            AutomationPeer peerFromPoint = peer.GetPeerFromPoint(point);
            return StaticWrap(peerFromPoint, peer);
        }
 
        // Return proxy representing currently focused element (if any)
        private object InContextGetFocus()
        {
            // Note: - what if a custom element - eg anchor in a text box - has focus?
            // won't have a UIElement there, can we even find the host?
            // If it implements Automation, can hand over to it, but if it doesn't,
            // would like nearest item, drill in using visual tree?
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            AutomationPeer focusedPeer = AutomationPeer.AutomationPeerFromInputElement(Keyboard.FocusedElement);
            return StaticWrap(focusedPeer, peer);
        }
 
        //  redirect to AutomationPeer
        private object InContextGetPatternProvider(object arg)
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return peer.GetWrappedPattern((int)arg);
        }
 
        // Return proxy representing element in specified direction (parent/next/firstchild/etc.)
        private object InContextNavigate( object arg )
        {
            NavigateDirection direction = (NavigateDirection) arg;
            AutomationPeer dest;
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
 
            switch( direction )
            {
                case NavigateDirection.Parent:
                    dest = peer.GetParent(); 
                    break;
                    
                case NavigateDirection.FirstChild:
                    if (!peer.IsInteropPeer)
                    {
                        dest = peer.GetFirstChild(); 
                    }
                    else
                    {
                        return peer.GetInteropChild();
                    }
                    break;
                    
                case NavigateDirection.LastChild:
                    if (!peer.IsInteropPeer)
                    {
                        dest = peer.GetLastChild(); 
                    }
                    else
                    {
                        return peer.GetInteropChild();
                    }
                    break;
                    
                case NavigateDirection.NextSibling:
                    dest = peer.GetNextSibling(); 
                    break;
                    
                case NavigateDirection.PreviousSibling:
                    dest = peer.GetPreviousSibling(); 
                    break;
                    
                default: 
                    dest = null; 
                    break;
            }
 
            return StaticWrap(dest, peer);
        }
 
    
        // Return value for specified property; or null if not supported
        private object InContextGetProviderOptions()
        {
            ProviderOptions options = ProviderOptions.ServerSideProvider;
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return options;
            }
            if (peer.IsHwndHost) 
                options |= ProviderOptions.OverrideProvider;
            
            return options;
        }
 
        // Return value for specified property; or null if not supported
        private object InContextGetPropertyValue ( object arg )
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return peer.GetPropertyValue((int)arg);
        }
 
        /// Returns whether this is the Root of the WCP tree or not
        private object InContextGetHostRawElementProvider( object unused )
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                return null;
            }
            return peer.GetHostRawElementProvider();
        }
 
        // Return unique ID for this element...
        private object InContextGetRuntimeId()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return peer.GetRuntimeId();
        }
 
        // Return bounding rectangle (screen coords) for this element...
        private object InContextBoundingRectangle()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            return peer.GetBoundingRectangle();
        }
 
        // Set focus to this element...
        private object InContextSetFocus()
        {
            AutomationPeer peer = Peer;
            if (peer == null)
            {
                throw new ElementNotAvailableException();
            }
            peer.SetFocus();
            return null;
        }
 
        // Return proxy representing the root of this WCP tree...
        private object InContextFragmentRoot()
        {
            AutomationPeer peer = Peer;
            AutomationPeer root = peer;
            if (root == null)
            {
                return null;
            }
            while(true)
            {
                AutomationPeer parent = root.GetParent();
                if(parent == null) break;
                root = parent;
            }
 
            return StaticWrap(root, peer);
        }
 
        #region disable switch for ElementProxy weak reference fix
 
        internal enum ReferenceType
        {
            Strong,
            Weak
        }
 
        // Returns RefrenceType.Strong if key AutomationWeakReferenceDisallow  under  
        // "HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework\Windows  Presentation Foundation\Features"
        // is set to 1 else returns ReferenceType.Weak. The registry key will be read only once.
        internal static ReferenceType AutomationInteropReferenceType
        {
            get
            {
                if (_shouldCheckInTheRegistry)
                {
                    if (RegistryKeys.ReadLocalMachineBool(RegistryKeys.WPF_Features, RegistryKeys.value_AutomationWeakReferenceDisallow))
                    {
                        _automationInteropReferenceType = ReferenceType.Strong;
                    }
                    _shouldCheckInTheRegistry = false;
                }
                return _automationInteropReferenceType;
            }
        }
        
        private static ReferenceType _automationInteropReferenceType = ReferenceType.Weak;
        private static bool _shouldCheckInTheRegistry = true;
 
        #endregion 
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Fields
 
        private readonly object _peer;
 
        #endregion Private Fields
    }
}