File: MS\Internal\AutomationProxies\WindowsUpDown.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: Win32 Up/Down proxy
 
using System;
using System.Windows;
using System.Collections;
using System.ComponentModel;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Text;
using System.Runtime.InteropServices;
using MS.Win32;
using NativeMethodsSetLastError = MS.Internal.UIAutomationClientSideProviders.NativeMethodsSetLastError;
 
namespace MS.Internal.AutomationProxies
{
    class WindowsUpDown : ProxyHwnd, IRangeValueProvider
    {
        // ------------------------------------------------------
        //
        // Constructors
        //
        // ------------------------------------------------------
 
        #region Constructors
 
        // Contructor for SpinControlProxy class. Calls the base class constructor.
        internal WindowsUpDown (IntPtr hwnd, ProxyFragment parent, int item)
            : base (hwnd, parent, item)
        {
            // Set the strings to return properly the properties.
            _cControlType = ControlType.Spinner;
 
            // support for events
            _createOnEvent = new WinEventTracker.ProxyRaiseEvents (RaiseEvents);
 
            // if spin is embedded in a tab control exclude from the content view.
            _fIsContent = !IsInsideOfTab();
        }
 
        #endregion
 
        #region Proxy Create
 
        // Static Create method called by UIAutomation to create this proxy.
        // returns null if unsuccessful
        internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild, int idObject)
        {
            return Create(hwnd, idChild);
        }
 
        internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild)
        {
            // Something is wrong if idChild is not zero
            ArgumentOutOfRangeException.ThrowIfNotEqual(idChild, 0);
 
            return new WindowsUpDown(hwnd, null, idChild);
        }
 
        // Called by the event tracker system.
        internal static void RaiseEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            if(idObject == NativeMethods.OBJID_CLIENT
                && eventId == NativeMethods.EventObjectInvoke
                && idProp == InvokePattern.InvokedEvent)
            {
                RaiseInvokedEvent(hwnd, idObject, idChild);
            }
            else if (idObject != NativeMethods.OBJID_VSCROLL && idObject != NativeMethods.OBJID_HSCROLL)
            {
                WindowsUpDown wtv = new WindowsUpDown (hwnd, null, -1);
                wtv.DispatchEvents (eventId, idProp, idObject, idChild);
            }
        }
 
        private static void RaiseInvokedEvent(IntPtr hwnd, int idObject, int idChild)
        {
            ProxySimple button = null;
            if (idChild == 1)
            {
                WindowsUpDown wtv = new WindowsUpDown(hwnd, null, -1);
                button = wtv.CreateSpinButtonItem(SpinItem.DownArrow);
            }
            else if (idChild == 2)
            {
                WindowsUpDown wtv = new WindowsUpDown(hwnd, null, -1);
                button = wtv.CreateSpinButtonItem(SpinItem.UpArrow);
            }
            if (button != null)
            {
                button.DispatchEvents(NativeMethods.EventObjectInvoke, InvokePattern.InvokedEvent, idObject, idChild);
            }
        }
 
        // Creates a list item RawElementBase Item
        private ProxySimple CreateSpinButtonItem (SpinItem item)
        {
            return new SpinButtonItem(_hwnd, IsSpinnerElement()? _parent : this, (int)item);
        }
 
        #endregion Proxy Create
 
        //------------------------------------------------------
        //
        //  Patterns Implementation
        //
        //------------------------------------------------------
 
        #region ProxySimple Interface
 
        // Returns a pattern interface if supported.
        internal override object GetPatternProvider (AutomationPattern iid)
        {
            return (iid == RangeValuePattern.Pattern) ? this : null;
        }
 
        internal override object GetElementProperty(AutomationProperty idProp)
        {
            if (idProp == AutomationElement.IsControlElementProperty)
            {
                // Hide spin portion in the logical tree
                // in the case when it is embedded inside of a winforms spinner
                if (WindowsFormsHelper.IsWindowsFormsControl(_hwnd) && IsWinformUpdown(_hwnd))
                {
                    return false;
                }
            }
 
            return base.GetElementProperty(idProp);
        }
 
        //Gets the localized name
        internal override string LocalizedName
        {
            get
            {
                return SR.LocalizedNameWindowsUpDown;
            }
        }
 
        #endregion
 
        #region ProxyFragment Interface
 
        // Returns the next sibling element in the raw hierarchy.
        // Peripheral controls have always negative values.
        // Returns null if no next child
        internal override ProxySimple GetNextSibling (ProxySimple child)
        {
            // Determine how many items are in the list view.
            if (child._item == (int)SpinItem.DownArrow)
            {
                return CreateSpinButtonItem (SpinItem.UpArrow);
            }
 
            return null;
        }
 
        // Returns the previous sibling element in the raw hierarchy.
        // Peripheral controls have always negative values.
        // Returns null is no previous child.
        internal override ProxySimple GetPreviousSibling (ProxySimple child)
        {
            if (child._item == (int)SpinItem.UpArrow)
            {
                return CreateSpinButtonItem (SpinItem.DownArrow);
            }
 
            return null;
        }
 
        // Returns the first child element in the raw hierarchy.
        internal override ProxySimple GetFirstChild ()
        {
            return CreateSpinButtonItem (SpinItem.DownArrow);
        }
 
        // Returns the last child element in the raw hierarchy.
        internal override ProxySimple GetLastChild ()
        {
            return CreateSpinButtonItem (SpinItem.UpArrow);
        }
 
        // Returns a Proxy element corresponding to the specified screen coordinates.
        internal override ProxySimple ElementProviderFromPoint (int x, int y)
        {
            for (SpinItem item = SpinItem.DownArrow; item <= SpinItem.UpArrow; item++)
            {
                NativeMethods.Win32Rect rc = new NativeMethods.Win32Rect (SpinButtonItem.GetBoundingRectangle (_hwnd, item));
 
                if (Misc.PtInRect(ref rc, x, y))
                {
                    return CreateSpinButtonItem (item);
                }
            }
 
            return this;
        }
 
        #endregion
 
        #region RangeValue Pattern
 
        // Change the position of the Up/Down
        void IRangeValueProvider.SetValue (double val)
        {
            // Make sure that the control is enabled
            if (!SafeNativeMethods.IsWindowEnabled (_hwnd))
            {
                throw new ElementNotEnabledException();
            }
 
            if (double.IsNaN(val))
            {
                throw new ArgumentException(SR.InvalidParameter);
            }
 
            if (val > Max)
            {
                throw new ArgumentOutOfRangeException("value", val, SR.RangeValueMax);
            }
            else if (val < Min)
            {
                throw new ArgumentOutOfRangeException("value", val, SR.RangeValueMin);
            }
 
            short newPos = Convert.ToInt16(val);
            Misc.ProxySendMessage(_hwnd, NativeMethods.UDM_SETPOS, IntPtr.Zero, NativeMethods.Util.MAKELPARAM(newPos, 0));
 
            // Scroll the buddy
            Misc.ProxySendMessage(HwndBuddy(_hwnd), NativeMethods.WM_HSCROLL, NativeMethods.Util.MAKELPARAM(NativeMethods.SB_THUMBPOSITION, newPos), IntPtr.Zero);
        }
 
        // Request to get the value that this UI element is representing in a native format
        double IRangeValueProvider.Value
        {
            get
            {
                return Pos;
            }
        }
 
        bool IRangeValueProvider.IsReadOnly
        {
            get
            {
                return false;
            }
        }
 
        double IRangeValueProvider.Maximum
        {
            get
            {
                return Max;
            }
        }
 
        double IRangeValueProvider.Minimum
        {
            get
            {
                return Min;
            }
        }
 
        double IRangeValueProvider.SmallChange
        {
            get
            {
                return 1.0;
            }
        }
 
        double IRangeValueProvider.LargeChange
        {
            get
            {
                return Double.NaN;
            }
        }
        #endregion RangeValuePattern
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        internal bool IsInsideOfTab()
        {
            IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(_hwnd, NativeMethods.GA_PARENT);
 
            if (hwndParent != IntPtr.Zero)
            {
                // Test for tab control
                return Misc.ProxyGetClassName(hwndParent).Contains("SysTabControl32");
            }
 
            return false;
        }
 
        // Method that verifies if window or one of its intermediate children (in terms of IAccessible tree) is a Spinner
        internal static bool IsWinformUpdown (IntPtr hwnd)
        {
            Accessible acc = null;
            int hr = Accessible.AccessibleObjectFromWindow(hwnd, NativeMethods.OBJID_CLIENT, ref acc);
 
            // Verify the role
            return hr == NativeMethods.S_OK && acc != null ? acc.Role == AccessibleRole.SpinButton : false;
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Internal Fields
        //
        //------------------------------------------------------
 
        #region Internal Fields
 
        internal enum SpinItem
        {
            DownArrow = 0,
            UpArrow = 1,
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        private double Pos
        {
            get
            {
                int pos = Misc.ProxySendMessageInt(_hwnd, NativeMethods.UDM_GETPOS, IntPtr.Zero, IntPtr.Zero);
 
                // From the doc, If successful, the high-order word is set to zero and the
                // low-order word is set to the control's current position. If
                // an error occurs, the high-order word is set to a nonzero value.
                // However as often the high word is set to 1 but the value is ok, ignore the
                // error code and just return the pos.
                return (double)NativeMethods.Util.LOWORD(pos);
            }
        }
 
        private static IntPtr HwndBuddy(IntPtr hwnd)
        {
            IntPtr hwndBuddy = Misc.ProxySendMessage(hwnd, NativeMethods.UDM_GETBUDDY, IntPtr.Zero, IntPtr.Zero);
 
            // if no buddy window, then all notifications are sent to the parent
            if (hwndBuddy == IntPtr.Zero)
            {
                hwndBuddy = Misc.GetParent(hwnd);
            }
            return hwndBuddy;
        }
 
        private bool IsSpinnerElement()
        {
            // If this is a Spinner UpDown Control, the buddy window should be a control with
            // the class of EDIT.
            IntPtr hwndBuddy = HwndBuddy(_hwnd);
            return hwndBuddy != IntPtr.Zero && Misc.ProxyGetClassName(hwndBuddy).IndexOf("EDIT", StringComparison.OrdinalIgnoreCase) != -1;
        }
 
        private double Max
        {
            get
            {
                // The low-order word is the maximum position for the control, and the
                // high-order word is the minimum position.
                int range = Misc.ProxySendMessageInt(_hwnd, NativeMethods.UDM_GETRANGE, IntPtr.Zero, IntPtr.Zero);
                int min = NativeMethods.Util.HIWORD(range);
                int max = NativeMethods.Util.LOWORD(range);
 
                return (double)(max > min ? max : min);
            }
        }
 
        private double Min
        {
            get
            {
                // The low-order word is the maximum position for the control, and the
                // high-order word is the minimum position.
                int range = Misc.ProxySendMessageInt(_hwnd, NativeMethods.UDM_GETRANGE, IntPtr.Zero, IntPtr.Zero);
                int min = NativeMethods.Util.HIWORD(range);
                int max = NativeMethods.Util.LOWORD(range);
 
                return (double)(max > min ? min : max);
            }
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  SpinButtonItem Private Class
        //
        //------------------------------------------------------
 
        #region SpinButtonItem
 
        class SpinButtonItem: ProxySimple, IInvokeProvider
        {
            //------------------------------------------------------
            //
            //  Constructors
            //
            //------------------------------------------------------
 
            #region Constructors
 
            // Contructor for SpinControlProxy class. Calls the base class constructor.
            internal SpinButtonItem (IntPtr hwnd, ProxyFragment parent, int item)
                : base(hwnd, parent, item)
            {
                // Set the strings to return properly the properties.
                _fIsContent = false;
 
                _cControlType = ControlType.Button;
 
                WindowsUpDown upDownParent = parent as WindowsUpDown;
                if (upDownParent != null)
                {
                    _isInsideOfTab = upDownParent.IsInsideOfTab();
                }
 
                // The buttons are swapped on a tab control compared to the spinner.
                if (_isInsideOfTab)
                {
                    item = 1 - item;
                }
 
                _sAutomationId = _asAutomationId[item];
            }
 
            #endregion
 
            //------------------------------------------------------
            //
            //  Patterns Implementation
            //
            //------------------------------------------------------
 
            #region ProxySimple Interface
 
            // Returns a pattern interface if supported.
            internal override object GetPatternProvider (AutomationPattern iid)
            {
                return iid == InvokePattern.Pattern ? this : null;
            }
 
            // Gets the bounding rectangle for this element
            internal override Rect BoundingRectangle
            {
                get
                {
                    return GetBoundingRectangle(_hwnd, (WindowsUpDown.SpinItem)_item);
                }
            }
 
            // Process all the Logical and Raw Element Properties
            internal override object GetElementProperty(AutomationProperty idProp)
            {
                if (idProp == AutomationElement.IsControlElementProperty)
                {
                    IntPtr hwndTabParent = GetTabParent();
                    if (hwndTabParent != IntPtr.Zero)
                    {
                        return WindowsTab.IsValidControl(hwndTabParent);
                    }
                }
 
                return base.GetElementProperty(idProp);
            }
 
            //Gets the localized name
            internal override string LocalizedName
            {
                get
                {
                    int item = _item;
 
                    // The buttons are swapped on a tab control compared to the spinner.
                    if (_isInsideOfTab)
                    {
                        item = 1 - item;
                    }
 
                    return SR.GetResourceString(_asNames[item]);
                }
            }
 
            #endregion ProxySimple Interface
 
            #region Invoke Pattern
 
            // Same as a click on one of the button Up or Down
            void IInvokeProvider.Invoke ()
            {
                // Make sure that the control is enabled
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    throw new ElementNotEnabledException();
                }
 
                // NOTE: The GetBoundingRectangel() will swap the buttons to retrieve
                // the correct rectangle based on the WS_EX_LAYOUTRTL bit.  But the
                // SendMessages WM_LBUTTONDOWN and WM_LBUTTONUP also swaps the buttons
                // on the WS_EX_LAYOUTRTL bit.  So need to send the center point of
                // button before the swap to get the SendMessage to apply it to the
                // correct button.
 
                int item = _item;
                // If the control is horizontal and the WS_EX_LAYOUTRTL is set need to
                // swap the button order
                if (IsHorizontal(_hwnd) && Misc.IsLayoutRTL(_hwnd))
                {
                    item = 1 - item;
                }
 
                // does the control have vertical scrolling buttons
                Rect rc = GetBoundingRectangle(_hwnd, (WindowsUpDown.SpinItem)item);
                NativeMethods.Win32Rect updownRect = new NativeMethods.Win32Rect();
 
                if (!Misc.GetWindowRect(_hwnd, ref updownRect))
                {
                    throw new InvalidOperationException(SR.OperationCannotBePerformed);
                }
 
                int x = (int) rc.Left - updownRect.left + (int) rc.Width / 2;
                int y = (int) rc.Top - updownRect.top + (int) rc.Height / 2;
                IntPtr center = NativeMethods.Util.MAKELPARAM (x, y);
 
                // the message does not seems to operate, fake a mouse action instead
                Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONDOWN, (IntPtr)NativeMethods.MK_LBUTTON, center);
                Misc.ProxySendMessage(_hwnd, NativeMethods.WM_LBUTTONUP, (IntPtr)NativeMethods.MK_LBUTTON, center);
            }
 
            #endregion Invoke Pattern
 
            //------------------------------------------------------
            //
            //  Internal Methods
            //
            //------------------------------------------------------
 
            #region Internal Methods
 
            static internal Rect GetBoundingRectangle(IntPtr hwnd, WindowsUpDown.SpinItem item)
            {
                NativeMethods.Win32Rect updownRect = new NativeMethods.Win32Rect();
 
                if (!Misc.GetWindowRect(hwnd, ref updownRect))
                {
                    return Rect.Empty;
                }
 
                bool fHorz = IsHorizontal(hwnd);
 
                // If the control is horizontal and the WS_EX_LAYOUTRTL is set need to
                // swap the button order
                if (fHorz && Misc.IsLayoutRTL(hwnd))
                {
                    item = item == SpinItem.DownArrow ? SpinItem.UpArrow : SpinItem.DownArrow;
                }
 
                switch (item)
                {
                    case WindowsUpDown.SpinItem.DownArrow:
                        if (fHorz)
                        {
                            int width = (updownRect.right - updownRect.left);
                            updownRect.right = updownRect.left + width / 2;
                        }
                        else
                        {
                            int height = (updownRect.bottom - updownRect.top);
                            updownRect.bottom = updownRect.top + height / 2;
                        }
                        // Don't need to normalize, GetWindowRect returns screen coordinates.
                        return updownRect.ToRect(false);
 
                    case WindowsUpDown.SpinItem.UpArrow:
                        if (fHorz)
                        {
                            int width = (updownRect.right - updownRect.left);
                            updownRect.left = updownRect.left + width / 2;
                        }
                        else
                        {
                            int height = (updownRect.bottom - updownRect.top);
                            updownRect.top = updownRect.top + height / 2;
                        }
                        // Don't need to normalize, GetWindowRect returns screen coordinates.
                        return updownRect.ToRect(false);
                }
 
                return Rect.Empty;
            }
 
            #endregion
 
            // ------------------------------------------------------
            //
            //  Private Methods
            //
            //------------------------------------------------------
 
            #region Private Methods
 
            private IntPtr GetTabParent()
            {
                IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(_hwnd, NativeMethods.GA_PARENT);
 
                if (hwndParent != IntPtr.Zero)
                {
                    // Test for tab control
                    hwndParent = Misc.ProxyGetClassName(hwndParent).Contains("SysTabControl32") ? hwndParent : IntPtr.Zero;
                }
 
                return hwndParent;
            }
 
            private static bool IsHorizontal(IntPtr hwnd)
            {
                return Misc.IsBitSet(Misc.GetWindowStyle(hwnd), NativeMethods.UDS_HORZ);
            }
 
            #endregion
 
            // ------------------------------------------------------
            //
            // Private Fields
            //
            // ------------------------------------------------------
 
            #region Private Fields
 
            private bool _isInsideOfTab;
 
            private static string [] _asNames = {
                nameof(SR.LocalizedNameWindowsSpinButtonItemForward),
                nameof(SR.LocalizedNameWindowsSpinButtonItemBackward)
            };
 
            private static string[] _asAutomationId = new string[] {
                "SmallIncrement", "SmallDecrement"  // This string is a non-localizable string
            };
 
            #endregion
        }
 
        #endregion
    }
}