File: MS\Internal\AutomationProxies\ProxySimple.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\UIAutomation\UIAutomationClientSideProviders\UIAutomationClientSideProviders.csproj (UIAutomationClientSideProviders)
// 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: Base class for all the Win32 and office Controls.
//
//              The ProxySimple class is the base class for the leafs
//              in the raw element tree. Also proxy that directly derives
//              from this clas should not care about events
//
//              The UIAutomation Simple class is limited to UI elements that are
//              Hwnd based. This makes it of little use for Win32 and office controls.
//              The ProxySimple class removes this limitation. This leads to a couple of
//              changes; RuntTimeID and BoundingRect must be implemented by this object.
//
//
//              Class ProxySimple: IRawElementProviderFragment, IRawElementProviderSimple
//                  BoundingRectangle
//                  RuntimeId
//                  Properties
//                  GetPatterns
//                  SetFocus
//
//              Example: ComboboxButton, MenuItem, Office CommandBar button, ListViewSubitem
//
//
//
 
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
using System;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Collections;
using Accessibility;
using System.Windows;
using MS.Win32;
 
namespace MS.Internal.AutomationProxies
{
    // Base Class for all the Windows Control.
    // Implements the default behavior
    //
    // The distinction between Proxy siblings is made through an ID (called _item).
    // The underlying hwnd is kept, as the proxy parent and a flag to _fSubtree.
    class ProxySimple : IRawElementProviderSimple, IRawElementProviderFragment
    {
        // ------------------------------------------------------
        //
        // Constructors
        //
        // ------------------------------------------------------
 
        #region Constructors
 
        // Constructor.
        // Param "hwnd" is the handle to underlying window
        // Param "parent" is the Parent, must be a ProxyFragment
        // Param "item" is the ID of item to represent
        internal ProxySimple(IntPtr hwnd, ProxyFragment parent, int item)
        {
            _hwnd = hwnd;
            _item = item;
            _parent = parent;
 
            // is element a leaf?
            _fSubTree = (_parent != null);
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Patterns Implementation
        //
        // ------------------------------------------------------
 
        #region ProxySimple Methods
 
        internal virtual ProviderOptions ProviderOptions
        {
            get
            {
                return ProviderOptions.ClientSideProvider;
            }
        }
 
        // Returns the bounding rectangle of the control.
        // If the control is not an hwnd, the subclass should implement this call
        internal virtual Rect BoundingRectangle
        {
            get
            {
                if (_hwnd == IntPtr.Zero)
                {
                    return Rect.Empty;
                }
 
                NativeMethods.Win32Rect controlRectangle = NativeMethods.Win32Rect.Empty;
 
                if (!Misc.GetWindowRect(_hwnd, ref controlRectangle))
                {
                    return Rect.Empty;
                }
                // Don't normalize, consumers & subclasses will normalize with conditionals
                return controlRectangle.ToRect(false);
            }
        }
 
        // Sets the focus to this item.
        // By default, fails
        internal virtual bool SetFocus()
        {
            return false;
        }
 
        // Returns the Run Time Id.
        //
        // The RunTimeID is a array of int with RID [0] set to 1 and RID [1] the hwnd
        // identifier. The remaining of the chain are values one per sub node in
        // the raw element tree.
        // Avalon and other none hwnd based elements have RunTimeIDs that never starts
        //  with '1'. This makes the RunTimeId for Win32 controls uniq.
        //
        // By default the _item data member is used as ID for each depth
        // in the element tree
        internal virtual int [] GetRuntimeId ()
        {
            if (_fSubTree && !IsHwndElement())
            {
                // add the id for this level at the end of the chain
                return Misc.AppendToRuntimeId(GetParent().GetRuntimeId(), _item);
            }
            else
            {
                // UIA handles runtimeID for the HWND part for us
                return null;
            }
        }
 
        // Get unique ID for this element...
        // This is the internal version of GetRuntimeId called when a complete RuntimeId is needed internally (e.g.
        // RuntimeId is needed to create StructureChangedEventArgs) vs when UIAutomation asks for a RuntimeId
        // through IRawElementProviderFragment. WCTL #32188 : We need a helper method in UIAutomationCore that takes
        // an hwnd and returns a RuntimeId. Symptom of this being broken is getting InvalidOperationException
        // during events with message: Value cannot be null.  Parameter name: runtimeId.
        internal int[] MakeRuntimeId()
        {
            int idLen = ( _fSubTree && !IsHwndElement() ) ? 3 : 2;
            int[] id = new int[idLen];
 
            // Base runtime id is the number indicating Win32Provider + hwnd
            id[0] = ProxySimple.Win32ProviderRuntimeIdBase;
            id[1] = _hwnd.ToInt32();
 
            // Append part id to make this unique
            if ( idLen == 3 )
            {
                id[2] = _item;
            }
            return id;
        }
 
        internal virtual IRawElementProviderSimple HostRawElementProvider
        {
            get
            {
                if (_hwnd == IntPtr.Zero || (GetParent() != null && GetParent()._hwnd == _hwnd))
                {
                    return null;
                }
 
                return AutomationInteropProvider.HostProviderFromHandle(_hwnd);
            }
        }
 
        internal virtual ProxySimple GetParent()
        {
            return _parent;
        }
 
        // Process all the Element Properties
        internal virtual object GetElementProperty(AutomationProperty idProp)
        {
            // we can handle some properties locally
            if (idProp == AutomationElement.LocalizedControlTypeProperty)
            {
                return _sType;
            }
            else if(idProp == AutomationElement.ControlTypeProperty)
            {
                return _cControlType != null ? (object)_cControlType.Id : null;
            }
            else if (idProp == AutomationElement.IsContentElementProperty)
            {
                return _item >= 0 && _fIsContent;
            }
            else if (idProp == AutomationElement.NameProperty)
            {
                return LocalizedName;
            }
            else if (idProp == AutomationElement.AccessKeyProperty)
            {
                return GetAccessKey();
            }
            else if (idProp == AutomationElement.IsEnabledProperty)
            {
                return Misc.IsEnabled(_hwnd);
            }
            else if (idProp == AutomationElement.IsKeyboardFocusableProperty)
            {
                return IsKeyboardFocusable();
            }
            else if (idProp == AutomationElement.ProcessIdProperty)
            {
                // Get the pid of the process that the HWND lives in, not the
                // pid that this proxy lives in
                uint pid;
                Misc.GetWindowThreadProcessId(_hwnd, out pid);
                return (int)pid;
            }
            else if (idProp == AutomationElement.ClickablePointProperty)
            {
                NativeMethods.Win32Point pt = new NativeMethods.Win32Point();
 
                if (GetClickablePoint(out pt, !IsHwndElement()))
                {
                    // Due to P/Invoke marshalling issues, the reurn value is in the
                    // form of a {x,y} array instead of using the Point datatype
                    return new double[] { pt.x, pt.y };
                }
 
                return AutomationElement.NotSupported;
            }
            else if (idProp == AutomationElement.HasKeyboardFocusProperty)
            {
                // Check first if the hwnd has the Focus
                // Punt if not the case, drill down otherwise
                // If already focused, leave as-is. Calling SetForegroundWindow
                // on an already focused HWND will remove focus!
                return Misc.GetFocusedWindow() == _hwnd ? IsFocused() : false;
            }
            else if (idProp == AutomationElement.AutomationIdProperty)
            {
                // PerSharp/PreFast will flag this as a warning 6507/56507: Prefer 'string.IsNullOrEmpty(_sAutomationId)' over checks for null and/or emptiness.
                // _sAutomationId being null is invalid, while being empty is a valid state.
                // The use of IsNullOrEmpty while hide this.
#pragma warning suppress 6507
                System.Diagnostics.Debug.Assert(_sAutomationId != null, "_sAutomationId is null!");
#pragma warning suppress 6507
                return _sAutomationId.Length > 0 ? _sAutomationId : null;
            }
            else if (idProp == AutomationElement.IsOffscreenProperty)
            {
                return IsOffscreen();
            }
            else if (idProp == AutomationElement.HelpTextProperty)
            {
                return HelpText;
            }
            else if (idProp == AutomationElement.FrameworkIdProperty)
            {
                return WindowsFormsHelper.IsWindowsFormsControl(_hwnd) ? "WinForm" : "Win32";
            }
 
            return null;
        }
 
        internal virtual bool IsKeyboardFocusable()
        {
            // if it curently has focus it is obviosly focusable
            if (Misc.GetFocusedWindow() == _hwnd && IsFocused())
            {
                return true;
            }
 
            // If it's visible and enabled it might be focusable 
            if (SafeNativeMethods.IsWindowVisible(_hwnd) && (bool)GetElementProperty(AutomationElement.IsEnabledProperty))
            {
                // If it is something that we know is focusable and have marked it that way in the specific
                // proxy it should be focusable.
                if (IsHwndElement())
                {
                    // For a control that has the WS_TABSTOP style set, it should be focusable.
                    // Toolbars are genrealy not focusable but the short cut toolbar on the start menu is.
                    // The WS_TABSTOP will pick this up.
                    if (Misc.IsBitSet(WindowStyle, NativeMethods.WS_TABSTOP))
                    {
                        return true;
                    }
                    else
                    {
                        return _fIsKeyboardFocusable;
                    }
                }
                else
                {
                    return _fIsKeyboardFocusable;
                }
            }
 
            return false;
        }
 
        internal virtual bool IsOffscreen()
        {
            Rect itemRect = BoundingRectangle;
 
            if (itemRect.IsEmpty)
            {
                return true;
            }
 
            // As per the specs, IsOffscreen only takes immediate parent-child relationship into account,
            // so we only need to check if this item in offscreen with respect to its immediate parent.
            ProxySimple parent = GetParent();
            if (parent != null )
            {
                if ((bool)parent.GetElementProperty(AutomationElement.IsOffscreenProperty))
                {
                    return true;
                }
 
                // Now check to see if this item in visible on its parent
                Rect parentRect = parent.BoundingRectangle;
                if (!parentRect.IsEmpty && !Misc.IsItemVisible(ref parentRect, ref itemRect))
                {
                    return true;
                }
            }
 
            // if this element is not on any monitor than it is off the screen.
            NativeMethods.Win32Rect itemWin32Rect = new NativeMethods.Win32Rect(itemRect);
            return UnsafeNativeMethods.MonitorFromRect(ref itemWin32Rect, UnsafeNativeMethods.MONITOR_DEFAULTTONULL) == IntPtr.Zero;
        }
 
        internal virtual string GetAccessKey()
        {
            // If the control is part of a dialog box or a form,
            // get the accelerator from the static preceding that control
            // on the dialog.
            if (GetParent() == null && (bool)GetElementProperty(AutomationElement.IsKeyboardFocusableProperty))
            {
                string sRawName = Misc.GetControlName(_hwnd, false);
 
                return string.IsNullOrEmpty(sRawName) ? null : Misc.AccessKey(sRawName);
            }
 
            return null;
        }
 
        // Returns a pattern interface if supported.
        internal virtual object GetPatternProvider(AutomationPattern iid)
        {
            return null;
        }
 
        internal virtual ProxySimple[] GetEmbeddedFragmentRoots()
        {
            return null;
        }
 
        //Gets the controls help text
        internal virtual string HelpText
        {
            get
            {
                return null;
            }
        }
 
        // Gets the localized name
        internal virtual string LocalizedName
        {
            get
            {
                return null;
            }
        }
 
        #endregion
 
        #region Dispatch Event
 
        // Dispatch WinEvent notifications
        //
        // A Generic mechanism is implemented to support most WinEvents.
        // On reception of a WinEvent, a Proxy is created and a call to this method
        // is made. This method then raises a UIAutomation Events based on the
        // WinEvents IDs. The old value for a property is always set to null
        internal virtual void DispatchEvents(int eventId, object idProp, int idObject, int idChild)
        {
            EventManager.DispatchEvent(this, _hwnd, eventId, idProp, idObject);
        }
 
        internal virtual void RecursiveRaiseEvents(object idProp, AutomationPropertyChangedEventArgs e)
        {
            return;
        }
 
 
        #endregion
 
        #region IRawElementProviderSimple
 
        // ------------------------------------------------------
        //
        // Default implementation for the IRawElementProviderSimple.
        // Maps the UIAutomation methods to ProxySimple methods.
        //
        // ------------------------------------------------------
 
        ProviderOptions IRawElementProviderSimple.ProviderOptions
        {
            get
            {
                return ProviderOptions;
            }
        }
 
        // Return the context associated with this element
        IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider
        {
            get
            {
                return HostRawElementProvider;
            }
        }
 
        // Request the closest rectangle encompassing this element
        Rect IRawElementProviderFragment.BoundingRectangle
        {
            get
            {
                // Spec says that if an element is offscreen, we have the option of letting
                // the rect pass through or returning Rect.Empty; here, we intentionally
                // let it pass through as a convenience for MITA.
 
                // ProxySimple.BoundingRectanlgle
                return BoundingRectangle;
            }
        }
 
        // Request to return the element in the specified direction
        // ProxySimple object are leaf so it returns null except for the parent
        IRawElementProviderFragment IRawElementProviderFragment.Navigate(NavigateDirection direction)
        {
            System.Diagnostics.Debug.Assert(_parent != null, "Navigate: Leaf element does not have parent");
            switch (direction)
            {
                case NavigateDirection.Parent:
                    {
                        return GetParent();
                    }
 
                case NavigateDirection.NextSibling:
                    {
                        // NOTE: Do not use GetParent(), call _parent explicitly
                        return _parent.GetNextSibling(this);
                    }
 
                case NavigateDirection.PreviousSibling:
                    {
                        // NOTE: Do not use GetParent(), call _parent explicitly
                        return _parent.GetPreviousSibling(this);
                    }
            }
            return null;
        }
 
        IRawElementProviderFragmentRoot IRawElementProviderFragment.FragmentRoot
        {
            // NOTE: The implementation below is correct one.
            //       DO NOT CHANGE IT, since things will break
            //       There can be only 1 ROOT for each constellation                                   
            get
            {
                // Traverse up the parents until you find a node with no parents, this is the root
                ProxySimple walk = this;
 
                while (walk.GetParent() != null)
                {
                    walk = walk.GetParent();
                }
 
                return walk as IRawElementProviderFragmentRoot;
            }
        }
 
        // Returns the Run Time Id, an array of ints as the concatenation of IDs.
        int [] IRawElementProviderFragment.GetRuntimeId ()
        {
            //ProxySimple.GetRuntimeId ();
            return GetRuntimeId ();
        }
 
        // Returns a pattern interface if supported.
        object IRawElementProviderSimple.GetPatternProvider(int patternId)
        {
            AutomationPattern iid = AutomationPattern.LookupById(patternId);
            return GetPatternProvider(iid);
        }
 
        // Returns a given property
        // UIAutomation as a generic call for all the properties for all the patterns.
        // This routine splits properties per pattern and calls the appropriate routine
        // within a pattern.
        // A default implementation is provided for some properties
        object IRawElementProviderSimple.GetPropertyValue(int propertyId)
        {
            AutomationProperty idProp = AutomationProperty.LookupById(propertyId);
            return GetElementProperty(idProp);
        }
 
        // If this UI is capable of hosting other UI that also supports UIAutomation,
        // and the subtree rooted at this element contains such hosted UI fragments,
        // this should return an array of those fragments.
        //
        // If this UI does not host other UI, it may return null.
        IRawElementProviderSimple[] IRawElementProviderFragment.GetEmbeddedFragmentRoots()
        {
            return GetEmbeddedFragmentRoots();
        }
 
        // Request that focus is set to this item.
        // The UIAutomation framework will ensure that the UI hosting this fragment
        // is already focused before calling this method, so this method should only
        // update its internal focus state; it should not attempt to give its own
        // HWND the focus, for example.
        void IRawElementProviderFragment.SetFocus()
        {
            // Make sure that the control is enabled
            if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
            {
                throw new ElementNotEnabledException();
            }
 
            // A number of the Override Proxies return null from the GetElementProperty() method.  If 
            // a SetFocus() was called on them, the case to bool will cause a NullReferenceException.
            // So make sure the return is of type bool before casting.
            bool isKeyboardFocusable = true;
            object isKeyboardFocusableProperty = GetElementProperty(AutomationElement.IsKeyboardFocusableProperty);
            if (isKeyboardFocusableProperty is bool)
            {
                isKeyboardFocusable = (bool)isKeyboardFocusableProperty;
            }
 
            // UIAutomation already focuses the containing HWND for us, so only need to
            // set focus on the item within that...
            if (isKeyboardFocusable)
            {
                // Then set the focus on this item (virtual methods)
                SetFocus();
                return;
            }
 
            throw new InvalidOperationException(SR.SetFocusFailed);
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // Returns the clickable point on the element
        // In the case when clickable point is obtained - method returns true
        // In the case when clickable point cannot be obtained - method returns false
        internal bool GetClickablePoint(out NativeMethods.Win32Point pt, bool fClipClientRect)
        {
            NativeMethods.Win32Rect rcItem = new NativeMethods.Win32Rect(BoundingRectangle);
 
            // Intersect the bounding Rectangle with the client rectangle for framents
            // and simple items - use the override flag (used mostly for the non client area
            if (fClipClientRect && !_fNonClientAreaElement)
            {
                NativeMethods.Win32Rect rcOutside = new NativeMethods.Win32Rect();
 
                Misc.GetClientRectInScreenCoordinates(_hwnd, ref rcOutside);
 
                if (!Misc.IntersectRect(ref rcItem, ref rcOutside, ref rcItem))
                {
                    pt.x = pt.y = 0;
                    return false;
                }
            }
 
            ArrayList alIn = new ArrayList(100);
            ArrayList alOut = new ArrayList(100);
 
            // Get the mid point to start with
            pt.x = (rcItem.right - 1 + rcItem.left) / 2;
            pt.y = (rcItem.bottom - 1 + rcItem.top) / 2;
            alOut.Add(new ClickablePoint.CPRect(ref rcItem, true));
 
            // First go through all the children to exclude whatever is on top
            ProxyFragment proxyFrag = this as ProxyFragment;
            if (proxyFrag != null)
            {
                ClickablePoint.ExcludeChildren(proxyFrag, alIn, alOut);
            }
 
            return ClickablePoint.GetPoint(_hwnd, alIn, alOut, ref pt);
        }
 
        internal string GetAccessibleName(int item)
        {
            string name = null;
 
            IAccessible acc = AccessibleObject;
            if (acc != null)
            {
                name = acc.get_accName(item);
                name = string.IsNullOrEmpty(name) ? null : name;
            }
 
            return name;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Internal Properties
        //
        // ------------------------------------------------------
 
        #region Internal Properties
 
        // Returns the IAccessible interface for the container object
        internal virtual IAccessible AccessibleObject
        {
            get
            {
                if (_IAccessible == null)
                {
                    Accessible acc = null;
                    // We need to go search for it
                    _IAccessible = Accessible.AccessibleObjectFromWindow(_hwnd, NativeMethods.OBJID_CLIENT, ref acc) == NativeMethods.S_OK ? acc.IAccessible : null;
                }
 
                return _IAccessible;
            }
            set
            {
                _IAccessible = value;
            }
        }
 
        // Get the hwnd for this element
        internal IntPtr WindowHandle
        {
            get
            {
                return _hwnd;
            }
        }
 
        // Reference to the window Handle
        internal int WindowStyle
        {
            get
            {
                return Misc.GetWindowStyle(_hwnd);
            }
        }
 
        //Gets the extended style of the window
        internal int WindowExStyle
        {
            get
            {
                return Misc.GetWindowExStyle(_hwnd);
            }
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Internal Fields
        //
        // ------------------------------------------------------
 
        #region Internal Fields
 
        // Reference to the window Handle
        internal IntPtr _hwnd;
 
        // Is used to discriminate between items in a collection.
        internal int _item;
 
        // Parent of a subtree.
        internal ProxyFragment _parent;
 
        // Localized Control type name.  If the control has a ControlType this should not be set.
        internal string _sType;
 
        // Must be set by a subclass, used to return the automation id
        internal string _sAutomationId = "";
 
        // Used by the IsFocussable Property.
        // By default all elements are not Keyboard focusable, overide this flag
        // to change the default behavior.
        internal bool _fIsKeyboardFocusable;
 
        // Top level Desktop window
        internal static IntPtr _hwndDesktop = UnsafeNativeMethods.GetDesktopWindow();
 
        // Identifies an element as hwnd-based; used as the first value in RuntimeId for Win32 providers.
        internal const int Win32ProviderRuntimeIdBase = 1;
 
        #endregion
 
 
        // ------------------------------------------------------
        //
        // Protected Methods
        //
        // ------------------------------------------------------
 
        #region Protected Methods
 
        // This routine is only called on elements belonging to an hwnd
        // that has the focus.
        // Overload this routine for sub elements within an hwnd that can
        // have the focus, tab items, listbox items ...
        // The default implemention is to return true for proxy element 
        // that are hwnd.
        protected virtual bool IsFocused ()
        {
            return this is ProxyHwnd;
        }
 
        protected bool IsHwndElement()
        {
            return this is ProxyHwnd;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Protected Fields
        //
        // ------------------------------------------------------
 
        #region Protected Fields
 
        // True if this is a WindowsForms control.
        // This value is cached and calculated only when needed
        protected WindowsFormsHelper.FormControlState _windowsForms = WindowsFormsHelper.FormControlState.Undeterminate;
 
        // Which Controltype it is, Must be set by a subclass
        protected ControlType _cControlType;
 
        // Must be set by a subclass,
        // Prevents the generic generation of the persistent IDs
        protected bool _fHasPersistentID = true;
 
        // Used by the GetClickablePoint Logic to figure out if clipping 
        // must happen on the Client Rect or the Non ClientRect
        protected bool _fNonClientAreaElement;
 
        // true if the parent is of type ProxyFragment
        protected bool _fSubTree;
 
        // Tells whether then control is Content or Peripheral
        protected bool _fIsContent = true;
 
        // The IAccessible interface associated with this node
        protected IAccessible _IAccessible;
 
        #endregion
     }
}