File: MS\Internal\AutomationProxies\ProxyHwnd.cs
Project: wpf\src\UIAutomation\Win32Providers\UIAutomationClientsideProviders.csproj (UIAutomationClientsideProviders)
//---------------------------------------------------------------------------
//
// <copyright file="ProxyHwnd.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
// 
//
// Description: Base class for all the Win32 and office Controls.
//
//
//              Only true ROOT object should derive from this class
//              NOTE: In the case when ProxyHwnd.ElementProviderFromPoint 
//                    returns provider of type ProxyFragment we MUST do the further drilling ourselves
//                    since UIAutomation will not (and correctly!!!) do it. (see ProxyFragment's
//                    comments on how to implement this and WindowsListView.cs for example)
//
//              Class ProxyHwnd: ProxyFragment, IRawElementProviderAdviseEvents
//                  AdviseEventAdded
//                  AdviseEventRemoved
//
//
//              
// History:  
//  07/01/2003 : a-jeanp Created
//  08/21/03:    alexsn  Class explanations
//
//---------------------------------------------------------------------------
 
// PRESHARP: In order to avoid generating warnings about unkown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691
 
using System;
using System.Text;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Collections;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Globalization;
using System.ComponentModel;
using MS.Win32;
 
namespace MS.Internal.AutomationProxies
{
    #region ProxyHwnd
 
    // Base Class for all the Windows Control that handle context.
    // Implements the default behavior
    class ProxyHwnd : ProxyFragment, IRawElementProviderAdviseEvents
    {
        // ------------------------------------------------------
        //
        // Constructors
        //
        // ------------------------------------------------------
 
        #region Constructors
 
        internal ProxyHwnd (IntPtr hwnd, ProxyFragment parent, int item) 
            : base (hwnd, parent, item)
        {
        }
        #endregion
 
        //------------------------------------------------------
        //
        //  Patterns Implementation
        //
        //------------------------------------------------------
 
        #region ProxyHwnd Methods
 
        // ------------------------------------------------------
        //
        // Internal Methods
        //
        // ------------------------------------------------------
 
        // Advises proxy that an event has been added.
        // Maps the Automation Events into WinEvents and add those to the list of WinEvents notification hooks
        internal virtual void AdviseEventAdded (AutomationEvent eventId, AutomationProperty [] aidProps)
        {
            // No RawElementBase creation callback, exit from here
            if (_createOnEvent == null)
            {
                return;
            }
 
            int cEvents = 0;
            WinEventTracker.EvtIdProperty [] aEvents;
 
            // Gets an Array of WinEvents to trap on a per window handle basis
            if (eventId == AutomationElement.AutomationPropertyChangedEvent)
            {
                aEvents = PropertyToWinEvent (aidProps, out cEvents);
            }
            else
            {
                aEvents = EventToWinEvent (eventId, out cEvents);
            }
 
            // If we have WinEvents to trap, add those to the list of WinEvent
            // notification list
            if (cEvents > 0)
            {
                WinEventTracker.AddToNotificationList (_hwnd, _createOnEvent, aEvents, cEvents);
            }
        }
 
        // Advises proxy that an event has been removed.
        internal virtual void AdviseEventRemoved(AutomationEvent eventId, AutomationProperty [] aidProps)
        {
            // No RawElementBase creation callback, exit from here
            if (_createOnEvent == null)
            {
                return;
            }
 
            int cEvents;
            WinEventTracker.EvtIdProperty [] aEvents;
 
            // Gets an Array of WinEvents to trap on a per window handle basis
            if (eventId == AutomationElement.AutomationPropertyChangedEvent)
            {
                aEvents = PropertyToWinEvent (aidProps, out cEvents);
            }
            else
            {
                aEvents = EventToWinEvent (eventId, out cEvents);
            }
 
            // If we have WinEvents to remove, remive those to the list of WinEvent
            // notification list
            if (cEvents > 0)
            {
                WinEventTracker.RemoveToNotificationList (_hwnd, aEvents, null, cEvents);
            }
        }
 
        // Returns a proxy element corresponding to the specified screen coordinates.
        // For an hwnd element, the default behavior is to let UIAutomation do the work. 
        internal override ProxySimple ElementProviderFromPoint (int x, int y)
        {
            // Optimisation. If the point is within the client area return this, otherwise it could the the 
            // non client area. It would be better to return null all the time but this will lead to 
            // object to be created twice.
            return PtInClientRect (_hwnd, x, y) ? this : null;
        }
 
        internal override string GetAccessKey()
        {
            string accessKey = base.GetAccessKey();
            if ((bool)GetElementProperty(AutomationElement.IsKeyboardFocusableProperty))
            {
                if (string.IsNullOrEmpty(accessKey))
                {
                    accessKey = GetLabelAccessKey(_hwnd);
                }
            }
            return accessKey;
        }
 
        // Process all the Element Properties
        internal override object GetElementProperty (AutomationProperty idProp)
        {
            // if the hwnd is a winform, then return the Winform id otherwise let
            // UIAutomation do the job
            if (idProp == AutomationElement.AutomationIdProperty)
            {
                // Microsoft have a special way to obtain the id
                if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd, ref _windowsForms))
                {
                    string sPersistentID = WindowsFormsHelper.WindowsFormsID (_hwnd);
                    return string.IsNullOrEmpty(sPersistentID) ? null : sPersistentID;
                }
            }
            else if (idProp == AutomationElement.NameProperty)
            {
                string name;
                // If this is a Microsoft control and the AccessibleName is set, use it.
                if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd, ref _windowsForms))
                {
                    name = GetAccessibleName(NativeMethods.CHILD_SELF);
 
                    if (!string.IsNullOrEmpty(name))
                    {
                        return name;
                    }
                }
 
                // Only hwnd's can be labeled.
                name = LocalizedName;
 
                // PerSharp/PreFast will flag this as a warning 6507/56507: Prefer 'string.IsNullOrEmpty(name)' over checks for null and/or emptiness.
                // It is valid to set LocalizedName to an empty string.  LocalizedName being an
                // empty string will prevent the SendMessage(WM_GETTEXT) call.
#pragma warning suppress 6507
                if (name == null && GetParent() == null)
                {
                    if (_fControlHasLabel)
                    {
                        IntPtr label = Misc.GetLabelhwnd(_hwnd);
                        name = Misc.GetControlName(label, true);
                        if (!string.IsNullOrEmpty(name))
                        {
                            _controlLabel = label;
                        }
                    }
                    else
                    {
                        name = Misc.ProxyGetText(_hwnd);
                    }
                }
 
 
                // If name is still null, and we have an IAccessible, try it:
                // this picks up names on HWNDs set through Dynamic Annotation
                // (eg. on the richedits in Windows Mail), and holds us over till
                // we add DA support to UIA properly.
                if (String.IsNullOrEmpty(name))
                {
                    name = GetAccessibleName(NativeMethods.CHILD_SELF);
                }
                return name;
            }
            // Only hwnd's can be labeled.
            else if (idProp == AutomationElement.LabeledByProperty && _fControlHasLabel)
            {
                // This is called to make sure that _controlLabel gets set.
                object name = GetElementProperty(AutomationElement.NameProperty);
 
                // If a control has a LocalizedName, the _controlLabel will not get set.
                // So look for it now.
                if (_controlLabel == IntPtr.Zero && name != null && GetParent() == null)
                {
                    _controlLabel = Misc.GetLabelhwnd(_hwnd);
                }
 
                // If we have a cached _controlLabel that means that the name property we just got 
                // was retreived from the label of the control and not its text or something else.  If this 
                // is the case expose it as the label.
                if (_controlLabel != IntPtr.Zero)
                {
                    return AutomationInteropProvider.HostProviderFromHandle(_controlLabel);
                }
            }
            else if (idProp == AutomationElement.IsOffscreenProperty)
            {
                if (!SafeNativeMethods.IsWindowVisible(_hwnd))
                {
                    return true;
                }
 
                IntPtr hwndParent = Misc.GetParent(_hwnd);
                // Check if rect is within rect of parent. Don't do this for top-level windows,
                // however, since the win32 desktop hwnd claims to have a rect only as large as the
                // primary monitor, making hwnds on other monitors seem clipped.
                if (hwndParent != IntPtr.Zero && hwndParent != UnsafeNativeMethods.GetDesktopWindow())
                {
                    NativeMethods.Win32Rect parentRect = NativeMethods.Win32Rect.Empty;
                    if (Misc.GetClientRectInScreenCoordinates(hwndParent, ref parentRect) && !parentRect.IsEmpty)
                    {
                        Rect itemRect = BoundingRectangle;
 
                        if (!itemRect.IsEmpty && !Misc.IsItemVisible(ref parentRect, ref itemRect))
                        {
                            return true;
                        }
                    }
                }
            }
 
            return base.GetElementProperty(idProp);
        }
 
        // Gets the controls help text
        internal override string HelpText
        {
            get
            {
                int idChild = Misc.GetWindowId(_hwnd);
                string text = Misc.GetItemToolTipText(_hwnd, IntPtr.Zero, idChild);
                if (string.IsNullOrEmpty(text))
                {
                    text = Misc.GetItemToolTipText(_hwnd, IntPtr.Zero, 0);
                }
 
                if (string.IsNullOrEmpty(text))
                {
                    Accessible acc = Accessible.Wrap(AccessibleObject);
                    if (acc != null)
                    {
                        text = acc.Description;
                    }
                }
 
                return text;
            }
        }
 
        #endregion
 
        #region IRawElementProviderAdviseEvents Interface
 
        // ------------------------------------------------------
        //
        // IRawElementProviderAdviseEvents interface implementation
        //
        // ------------------------------------------------------
        // Advises event sinks that an event has been added.
        void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventIdAsInt, int[] propertiesAsInts)
        {
            AutomationEvent eventId = AutomationEvent.LookupById(eventIdAsInt);
            AutomationProperty [] properties = null;
            if (propertiesAsInts != null)
            {
                properties = new AutomationProperty[propertiesAsInts.Length];
                for (int i = 0; i < propertiesAsInts.Length; i++)
                {
                    properties[i] = AutomationProperty.LookupById(propertiesAsInts[i]);
                }
            }
 
            //ProxyHwnd.AdviseEventAdded
            AdviseEventAdded (eventId, properties);
        }
 
        // Advises event sinks that an event has been removed.
        void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventIdAsInt, int[] propertiesAsInts)
        {
            AutomationEvent eventId = AutomationEvent.LookupById(eventIdAsInt);
            AutomationProperty [] properties = null;
            if (propertiesAsInts != null)
            {
                properties = new AutomationProperty[propertiesAsInts.Length];
                for (int i = 0; i < propertiesAsInts.Length; i++)
                {
                    properties[i] = AutomationProperty.LookupById(propertiesAsInts[i]);
                }
            }
 
            //ProxyHwnd.AdviseEventRemoved
            AdviseEventRemoved (eventId, properties);
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Internal Fields
        //
        // ------------------------------------------------------
 
        #region Internal Fields
 
        // Callback into the Proxy code to create a raw element based on a WinEvent callback parameters
        internal WinEventTracker.ProxyRaiseEvents _createOnEvent = null;
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Protected Methods
        //
        // ------------------------------------------------------
 
        #region Protected Methods
 
        // Picks a WinEvent to track for a UIA property
        protected virtual int [] PropertyToWinEvent (AutomationProperty idProp)
        {
            if (idProp == AutomationElement.HasKeyboardFocusProperty)
            {
                return new int [] { NativeMethods.EventObjectFocus };
            }
            else if (idProp == AutomationElement.NameProperty)
            {
                return new int[] { NativeMethods.EventObjectNameChange };
            }
            else if (idProp == ValuePattern.ValueProperty || idProp == RangeValuePattern.ValueProperty)
            {
                return new int[] { NativeMethods.EventObjectValueChange };
            }
            else if (idProp == AutomationElement.IsOffscreenProperty)
            {
                return new int[] { NativeMethods.EventObjectLocationChange };
            }
            else if (idProp == ExpandCollapsePattern.ExpandCollapseStateProperty)
            {
                return new int [] { NativeMethods.EventObjectStateChange,
                                    NativeMethods.EventObjectShow,
                                    NativeMethods.EventObjectHide};
            }
 
            // Windows sent OBJECT_VALUECHANGE for changes in the scroll bar with the idObject set to the scroll bar id of the originator
            else if ((idProp == ScrollPattern.HorizontalScrollPercentProperty || 
                idProp == ScrollPattern.VerticalScrollPercentProperty) || 
                idProp == ScrollPattern.HorizontalViewSizeProperty ||
                idProp == ScrollPattern.VerticalViewSizeProperty )
            {
                return new int [] { NativeMethods.EventObjectValueChange };
            }
            else if (idProp == SelectionItemPattern.IsSelectedProperty)
            {
                return new int [] { NativeMethods.EventObjectSelectionAdd, 
                                    NativeMethods.EventObjectSelectionRemove, 
                                    NativeMethods.EventObjectSelection};
            }
            else if (idProp == TogglePattern.ToggleStateProperty)
            {
                return new int[] { NativeMethods.EventSystemCaptureEnd,
                                   NativeMethods.EventObjectStateChange };
            }
 
            return null;
        }
 
        // Builds a list of Win32 WinEvents to process a UIAutomation Event.
        protected virtual WinEventTracker.EvtIdProperty [] EventToWinEvent (AutomationEvent idEvent, out int cEvent)
        {
            // Fill this variable with a WinEvent id if found
            int idWinEvent = 0;
 
            if (idEvent == SelectionItemPattern.ElementSelectedEvent)
            {
                cEvent = 2;
                return new WinEventTracker.EvtIdProperty[2]
                {
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectSelection, idEvent), 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectStateChange, idEvent)
                };
            }
            else if (idEvent == SelectionItemPattern.ElementAddedToSelectionEvent)
            {
                // For some control, the Event Selection is sent instead of SelectionAdd
                // Trap both.
                cEvent = 2;
                return new WinEventTracker.EvtIdProperty [2]
                {
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectSelectionAdd, idEvent), 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectSelection, idEvent)
                };
            }
            else if (idEvent == SelectionItemPattern.ElementRemovedFromSelectionEvent)
            {
                idWinEvent = NativeMethods.EventObjectSelectionRemove;
            }
            else if (idEvent == SelectionPattern.InvalidatedEvent)
            {
                idWinEvent = NativeMethods.EventObjectSelectionWithin;
            }
            else if (idEvent == InvokePattern.InvokedEvent)
            {
                cEvent = 4;
                return new WinEventTracker.EvtIdProperty[4] { 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventSystemCaptureEnd, idEvent), // For SysHeaders
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectStateChange, idEvent),
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectValueChange, idEvent), // For WindowsScrollBarBits
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectInvoke, idEvent)
                };
            }
            else if (idEvent == AutomationElement.StructureChangedEvent)
            {
                cEvent = 3;
                return new WinEventTracker.EvtIdProperty[3] { 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectCreate, idEvent), 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectDestroy, idEvent), 
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectReorder, idEvent) 
                };
            }
            else if (idEvent == TextPattern.TextSelectionChangedEvent)
            {
                cEvent = 2;
                return new WinEventTracker.EvtIdProperty[2] {
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectLocationChange, idEvent),
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectTextSelectionChanged, idEvent)
                };
            }
            else
            {
                cEvent = 0;
                return null;
            }
 
            // found one and only one
            cEvent = 1;
            return new WinEventTracker.EvtIdProperty [1] { new WinEventTracker.EvtIdProperty (idWinEvent, idEvent) };
        }
        
        // Check if a point is within the client Rect of a window
        static protected bool PtInClientRect (IntPtr hwnd, int x, int y)
        {
            NativeMethods.Win32Rect rc = new NativeMethods.Win32Rect ();
            if (!Misc.GetClientRect(hwnd, ref rc))
            {
                return false;
            }
 
            if (!Misc.MapWindowPoints(hwnd, IntPtr.Zero, ref rc, 2))
            {
                return false;
            }
            return x >= rc.left && x < rc.right && y >= rc.top && y < rc.bottom;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private Methods
        //
        // ------------------------------------------------------
 
        #region Private Methods
 
        // Get the access key of the associated label (if there is
        // an associated label).
        static protected string GetLabelAccessKey(IntPtr hwnd)
        {
            string accessKey = string.Empty;
            IntPtr label = Misc.GetLabelhwnd(hwnd);
            if (label != IntPtr.Zero)
            {
                string labelText = Misc.ProxyGetText(label);
                if (!string.IsNullOrEmpty(labelText))
                {
                    accessKey = Misc.AccessKey(labelText);
                }
            }
            return accessKey;
        }
 
        // Builds a list of Win32 WinEvents to process changes in properties changes values.
        // Returns an array of Events to Set. The number of valid entries in this array is pass back in cEvents
        private WinEventTracker.EvtIdProperty [] PropertyToWinEvent (AutomationProperty [] aProps, out int cEvent)
        {
            ArrayList alEvents = new ArrayList (16);
 
            foreach (AutomationProperty idProp in aProps)
            {
                int [] evtId = PropertyToWinEvent (idProp);
 
                for (int i = 0; evtId != null && i < evtId.Length; i++)
                {
                    alEvents.Add (new WinEventTracker.EvtIdProperty (evtId [i], idProp));
                }
 
            }
 
            WinEventTracker.EvtIdProperty [] aEvtIdProperties = new WinEventTracker.EvtIdProperty [alEvents.Count];
 
            cEvent = alEvents.Count;
            for (int i = 0; i < cEvent; i++)
            {
                aEvtIdProperties [i] = (WinEventTracker.EvtIdProperty) alEvents [i];
            }
            return aEvtIdProperties;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private Fields
        //
        // ------------------------------------------------------
 
        #region Private Fields
 
        // True if the control has an associated label.
        protected bool _fControlHasLabel = true;
 
        // The hwnd of static text that is functioning as a label for a control.
        // This is initialized in GetElementProperty for the Name property and
        // used by the LabeledBy property.
        // If !_fControlHasLabel, _controlLabel will be IntPtr.Zero.
        private IntPtr _controlLabel;
 
        #endregion
    }
    #endregion
}