File: MS\Internal\AutomationProxies\WinFormsSpinner.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: Spinner Proxy
 
using System;
using System.Collections;
using System.Text;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows;
using MS.Win32;
 
namespace MS.Internal.AutomationProxies
{
    // WinForms NumericUpDown proxy
    class WinformsSpinner : ProxyHwnd, IRawElementProviderHwndOverride, IRangeValueProvider, IValueProvider
    {
        // ------------------------------------------------------
        //
        // Constructors
        //
        // ------------------------------------------------------
 
        #region Constructors 
 
        // Contructor for WinformsSpinner class. Calls the base class constructor.
        internal WinformsSpinner(IntPtr hwnd, IntPtr hwndEdit, IntPtr hwndUpDown, ProxyFragment parent, int item)
            : base(hwnd, parent, item)
        {
            _elEdit = new WindowsEditBox(hwndEdit, this, (int)0);
            _elUpDown = new WindowsUpDown(hwndUpDown, this, (int)0);
 
            string text;
            try
            {
                text = Misc.ProxyGetText(hwndEdit);
            }
            catch (TimeoutException)
            {
                text = null;
            }
            catch (Win32Exception)
            {
                text = null;
            }
 
            if (!string.IsNullOrEmpty(text))
            {
                try
                {
                    double.Parse(text, NumberStyles.Any, CultureInfo.InvariantCulture);
 
                    // the text parsed just fine so must be a number.
                    _type = SpinnerType.Numeric;
                }
                catch (FormatException)
                {
                    // the text is not a based 10 number.  Check if the text is a hex number.
                    try
                    {
                        int.Parse(text, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
 
                        // the text parsed just fine so must be a number.
                        _type = SpinnerType.Numeric;
                    }
                    catch (FormatException)
                    {
                        // the text does not consist solely of an optional negative sign followed by a sequence of
                        // digits ranging from 0 to 9, so this spinner must be a domain spinner
                        _type = SpinnerType.Domain;
                    }
                    catch (OverflowException)
                    {
                        // the text represents a number less than MinValue or greater than MaxValue, but it is still
                        // a number, therefore must be a numeric spinner
                        _type = SpinnerType.Numeric;
                    }
                }
                catch (OverflowException)
                {
                    // the text represents a number less than MinValue or greater than MaxValue, but it is still
                    // a number, therefore must be a numeric spinner
                    _type = SpinnerType.Numeric;
                }
            }
            else
            {
                // numeric spinners always have a value.  The defualt state of a domain spinner
                // may be blank.  The text value is blank so this must be a domain spinner.
                _type = SpinnerType.Domain;
            }
 
            // Set the strings to return properly the properties.
            _cControlType = ControlType.Spinner;
 
            // support for events
            _createOnEvent = new WinEventTracker.ProxyRaiseEvents(WindowsUpDown.RaiseEvents);
        }
 
        #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)
        {
            // If idChild is not zero this can't be a Winforms spinner.  The way this
            // code is called, the passed in hwnd could be some other control and in
            // that case the calling code should continue looking for a valid proxy.
            if (idChild != 0)
            {
                return null;
            }
 
            try
            {
                //
                // A winform spinner control can only have 2 children - An Edit and an UpDown.
                // 
 
                // First child
                IntPtr hwndFirstChild = Misc.GetWindow(hwnd, NativeMethods.GW_CHILD);
                if (hwndFirstChild == IntPtr.Zero)
                {
                    return null;
                }
 
                // Last child
                IntPtr hwndLastChild = Misc.GetWindow(hwndFirstChild, NativeMethods.GW_HWNDLAST);
                if (hwndLastChild == IntPtr.Zero)
                {
                    return null;
                }
 
                // No children in the middle
                if (Misc.GetWindow(hwndFirstChild, NativeMethods.GW_HWNDNEXT) != hwndLastChild)
                {
                    return null;
                }
                
 
                // We need to positively identify the two children as Edit and UpDown controls
                IntPtr hwndEdit;
                IntPtr hwndSpin;
 
                // Find the Edit control.  Typically the UpDown is first so we'll start with the other window.                                
                if (Misc.ProxyGetClassName(hwndLastChild).IndexOf("Edit", StringComparison.OrdinalIgnoreCase) != -1)
                {
                    hwndEdit = hwndLastChild;
                    hwndSpin = hwndFirstChild;
                }
                else
                {
                    // Haven't seen this but suppose it's possible.  Subsequent test will confirm.
                    if (Misc.ProxyGetClassName(hwndFirstChild).IndexOf("Edit", StringComparison.OrdinalIgnoreCase) != -1)
                    {
                        hwndEdit = hwndFirstChild;
                        hwndSpin = hwndLastChild;
                    }
                    else
                    {
                        // Must contain an Edit/UpDown control pair
                        return null;
                    }
                }
                                
                //
                // A winform UpDown control can only have 2 children, both spin buttons.
                // 
 
                // Use IAccessible implementation to confirm the spinner & children
                Accessible acc = null;
                if (Accessible.AccessibleObjectFromWindow(hwndSpin, NativeMethods.OBJID_CLIENT, ref acc) != NativeMethods.S_OK || acc == null)
                {
                    return null;
                }
 
                if ((acc.Role != AccessibleRole.SpinButton) || (acc.ChildCount != 2))
                {
                    return null;
                }
                
                // Confirmed spinner
                return new WinformsSpinner(hwnd, hwndEdit, hwndSpin, null, 0);
            }
            catch (ElementNotAvailableException)
            {
                // Ignore ElementNotAvailableExceptions and return null
                return null;
            }
        }
 
        #endregion Proxy Create 
 
        //------------------------------------------------------
        //
        //  Patterns Implementation
        //
        //------------------------------------------------------
 
        // ------------------------------------------------------
        //
        // ProxySimple interface implementation
        //
        // ------------------------------------------------------
 
        #region ProxySimple Interface
 
        // Returns a pattern interface if supported.
        internal override object GetPatternProvider (AutomationPattern iid)
        {
            if (iid == RangeValuePattern.Pattern && _type == SpinnerType.Numeric)
            {
                return this;
            }
            else if (_type == SpinnerType.Domain)
            {
                if (iid == ValuePattern.Pattern)
                {
                    return this;
                }
                else if (iid == SelectionPattern.Pattern)
                {
                    // Special case for a WinForms domain spinner. It is supposed to support
                    // SelectionPattern, but because it is based on an edit control, this is
                    // not possible unless each of the items in the spinner have been reviewed
                    // at least once. Call out this unusual case by throwing the following
                    // NotImplementedException.
                    throw new NotImplementedException();
                }
            }
            return null;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // ProxyFragment interface implementation
        //
        // ------------------------------------------------------
 
        #region ProxyFragment Interface
 
        // Returns a Proxy element corresponding to the specified screen coordinates.
        internal override ProxySimple ElementProviderFromPoint(int x, int y)
        {
            Rect rcUpDown = _elUpDown.BoundingRectangle;
 
            if (rcUpDown.Contains(new Point(x, y)))
            {
                return _elUpDown.ElementProviderFromPoint (x, y);
            }
 
            return this;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // ProxyHwnd interface implementation
        //
        // ------------------------------------------------------
 
        #region ProxyHwnd Interface
 
        internal override void AdviseEventAdded(AutomationEvent eventId, AutomationProperty[] aidProps)
        {
            base.AdviseEventAdded(eventId, aidProps);
 
            // Need to also advise the edit portions of the spinner so that it can raise events.
            if (_elEdit != null)
            {
                _elEdit.AdviseEventAdded(eventId, aidProps);
            }
        }
 
        internal override void AdviseEventRemoved(AutomationEvent eventId, AutomationProperty[] aidProps)
        {
            base.AdviseEventRemoved(eventId, aidProps);
 
            // Need to also remove the advise from the edit portions of the spinner.
            if (_elEdit != null)
            {
                _elEdit.AdviseEventRemoved(eventId, aidProps);
            }
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // IRawElementProviderHwndOverride interface implementation
        //
        // ------------------------------------------------------
 
        #region IRawElementProviderHwndOverride
 
        IRawElementProviderSimple IRawElementProviderHwndOverride.GetOverrideProviderForHwnd(IntPtr hwnd)
        {
            // The Edit hwnd in the spinner is not a Logical Element
            // returns the full spinner on the request to get a provider for the edit
            if (IsEdit(hwnd))
            {
                return new WinformsSpinnerEdit(_hwnd, _elEdit._hwnd, _elUpDown._hwnd, _parent, _item);
            }
 
            return null;
        }
 
        #endregion IRawElementProviderHwndOverride
 
        // ------------------------------------------------------
        //
        // IRangeValueProvider interface implementation
        //
        // ------------------------------------------------------
 
        #region RangeValue Pattern
 
        // Sets a new position for the edit part of the spinner.
        void IRangeValueProvider.SetValue (double obj)
        {
            ((IRangeValueProvider)_elUpDown).SetValue(obj);
        }
 
        // Request to get the value that this UI element is representing in a native format
        double IRangeValueProvider.Value
        {
            get
            {
                return ((IRangeValueProvider)_elUpDown).Value;
            }
        }
 
        bool IRangeValueProvider.IsReadOnly
        {
            get
            {
                return (bool)((IRangeValueProvider)_elUpDown).IsReadOnly &&
                       (bool)((IValueProvider)_elEdit).IsReadOnly;
            }
        }
 
        double IRangeValueProvider.Maximum
        {
            get
            {
                return ((IRangeValueProvider)_elUpDown).Maximum;
            }
        }
 
        double IRangeValueProvider.Minimum
        {
            get
            {
                return ((IRangeValueProvider)_elUpDown).Minimum;
            }
        }
 
        double IRangeValueProvider.SmallChange
        {
            get
            {
                return ((IRangeValueProvider)_elUpDown).SmallChange;
            }
        }
 
        double IRangeValueProvider.LargeChange
        {
            get
            {
                return ((IRangeValueProvider)_elUpDown).LargeChange;
            }
        }
        #endregion RangeValuePattern
 
        #region Value Pattern
 
        // ------------------------------------------------------
        //
        // IValueProvider interface implementation
        //
        // ------------------------------------------------------
 
        // Sets a value into the edit box
        void IValueProvider.SetValue(string val)
        {
            ((IValueProvider)_elEdit).SetValue(val);
        }
 
        // Returns the value of the edit.
        string IValueProvider.Value
        {
            get
            {
                return ((IValueProvider)_elEdit).Value;
            }
        }
 
        // Returns if the edit control is read-only.
        bool IValueProvider.IsReadOnly
        {
            get
            {
                return true;
            }
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private methods                                             
        //
        // ------------------------------------------------------
 
        #region Private Methods
 
        // Check if the hwnd is the Edit Window in the spinner box
        private bool IsEdit(IntPtr hwnd)
        {
            return _elEdit._hwnd == hwnd;
        }
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private Fields                                             
        //
        // ------------------------------------------------------
 
        #region Private Fields
 
        private WindowsEditBox _elEdit;
        private WindowsUpDown _elUpDown;
 
        private SpinnerType _type;
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private Types
        //
        // ------------------------------------------------------
 
        #region Private Types
 
        // Button control types based on groupings of style constants
        private enum SpinnerType
        {
            Numeric,
            Domain
        };
 
        #endregion
 
        //------------------------------------------------------
        //
        //  WinformsSpinnerEdit Private Class
        //
        //------------------------------------------------------
 
        #region WinformsSpinnerEdit
 
        // Proxy for ComboBox Edit portion
        // The Edit proxy does not exist, it the the whole spinner. This class is needed
        // though to override ProviderOptions
        class WinformsSpinnerEdit : WinformsSpinner
        {
            //------------------------------------------------------
            //
            //  Constructors
            //
            //------------------------------------------------------
 
            #region Constructor
 
            internal WinformsSpinnerEdit(IntPtr hwnd, IntPtr hwndEdit, IntPtr hwndUpDown, ProxyFragment parent, int item)
            : base(hwnd, hwndEdit, hwndUpDown, parent, item)
            {
            }
 
            #endregion Constructor
 
            //------------------------------------------------------
            //
            //  Provider Implementation
            //
            //------------------------------------------------------
 
            internal override ProviderOptions ProviderOptions
            {
                get
                {
                    return base.ProviderOptions | ProviderOptions.OverrideProvider;
                }
            }
        }
 
        #endregion
    }
}