File: MS\Internal\AutomationProxies\WindowsComboBox.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 Combobox proxy
 
using System;
using System.Globalization;
using System.Text;
using System.ComponentModel;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows;
using System.Runtime.InteropServices;
using MS.Win32;
using NativeMethodsSetLastError = MS.Internal.UIAutomationClientSideProviders.NativeMethodsSetLastError;
 
namespace MS.Internal.AutomationProxies
{
    // Combobox Logical Tree and Patterns (NOTE: Edit is removed, by Design)
    //
    //  Combo box  (Commands, ExpandCollapse, Selection, Text, Value)
    //    --List box  (Selection, Scroll, Text)  [container]
    //        -----List item (Selection Item, Text)
    //        -----List item (Selection Item, Text)
    //        -----List item (Selection Item, Text)
    //    --DropDownButton peripheral  (Invoke)
    //
    // NOTE: We will reparent the List portion of the combo (ComboLBox) by
    //       1. We will provide a proxy for List portion (actually we will reuse WindowsListBox for it)
    //       2. List Host will refer to the ComboLBox hwnd
    //       3. Providing an ability to navigate from Combo to List and returtning Combo as
    //          List's parent
 
    // Combobox proxy
    class WindowsComboBox : ProxyHwnd, IValueProvider, IExpandCollapseProvider
    {
 
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructor
 
        WindowsComboBox (IntPtr hwnd, ProxyFragment parent, IntPtr hwndEx, int item)
            : base(hwnd, parent, item)
        {
            _cControlType = ControlType.ComboBox;
            _hwndEx = hwndEx;
            _comboType = GetComboType ();
            _fIsKeyboardFocusable = true;
            _createOnEvent = new WinEventTracker.ProxyRaiseEvents (RaiseEvents);
        }
 
 
        #endregion Constructor
 
        #region Proxy Create
 
        // Static Create method called by UIAutomation to create this proxy.
        // returns null if unsuccessful</returns>
        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 WindowsComboBox(hwnd, null, HostedByComboEx(hwnd), idChild);
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Pattern Implementation
        //
        //------------------------------------------------------
 
        #region ProxySimple Interface
 
        // Returns a pattern interface if supported.
        internal override object GetPatternProvider (AutomationPattern iid)
        {
            if (iid == ValuePattern.Pattern && IsEditableCombo())
            {
                return this;
            }
            else if (iid == ExpandCollapsePattern.Pattern && !IsSimpleCombo ())
            {
                return this;
            }
            else if (iid == SelectionPattern.Pattern)
            {
                // delegate work to the WindowsListBox implementation of ISelection
                ProxySimple list = CreateListBox ();
 
                if (list != null)
                {
                    ISelectionProvider selection = list.GetPatternProvider (iid) as ISelectionProvider;
 
                    if (selection != null)
                    {
                        return selection;
                    }
                }
            }
 
            return null;
        }
 
        // Gets the bounding rectangle for this element
        internal override Rect BoundingRectangle
        {
            get
            {
                NativeMethods.Win32Rect rcCombo = new NativeMethods.Win32Rect (base.BoundingRectangle);
 
                if (GetDroppedState (_hwnd))
                {
                    // NOTE: Do not use CB_GETDROPPEDCONTROLRECT
                    // it will not produce the correct rect
                    NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
                    if (GetComboInfo(_hwnd, ref cbInfo))
                    {
                        NativeMethods.Win32Rect rcList = NativeMethods.Win32Rect.Empty;
 
                        if (!Misc.GetWindowRect(cbInfo.hwndList, ref rcList))
                        {
                            return Rect.Empty;
                        }
                        if (!Misc.UnionRect(out rcCombo, ref rcCombo, ref rcList))
                        {
                            return Rect.Empty;
                        }
                    }
                }
 
                return rcCombo.ToRect(Misc.IsControlRTL(_hwnd));
            }
        }
 
        // Process all the Logical and Raw Element Properties
        internal override object GetElementProperty (AutomationProperty idProp)
        {
            if (idProp == AutomationElement.NameProperty)
            {
                if (Misc.GetClassName(_hwnd).Equals("Internet Explorer_TridentCmboBx"))
                {
                    object result = base.GetElementProperty(idProp);
                    // Return an empty string instead of null to prevent the default HWND proxy
                    // from using the window text as the name - since the window text for this owner-
                    // draw trident combo is garbage.
                    return result == null ? "" : (string)result;
                }
            }
            // EventManager.DispatchEvent() genericaly uses GetElementProperty()
            // to get properties during a property change event.  Proccess ExpandCollapseStateProperty
            // so the ExpandCollapseStateProperty Change Event can get the correct state.
            else if (idProp == ExpandCollapsePattern.ExpandCollapseStateProperty)
            {
                return ((IExpandCollapseProvider)this).ExpandCollapseState;
            }
 
            return base.GetElementProperty (idProp);
        }
 
        internal override ProxySimple [] GetEmbeddedFragmentRoots ()
        {
            // Only applies to drop-down lists, which reparent the list, need to do this...
            if (IsSimpleCombo())
                return null;
 
            // Because we are moving the listbox portion of the combo from the being a child of the
            // desktop to a child of the combo,  We have to tell automation the that this is one of our
            // children so the listbox will get advised of events added/removed.
            return new ProxySimple[] { CreateListBox() };
        }
 
        //Gets the localized name
        internal override string LocalizedName
        {
            get
            {
                // In the case of ComboBoxEx32, this element's _hwnd will be the
                // HWND of the ComboBox which is a child of ComboBoxEx the lable that names
                // Combobox however is a sibling of ComboBoxEx32, hence we need to handle this case here
                if (IsComboBoxEx32())
                {
                    return Misc.GetControlName(Misc.GetLabelhwnd(_hwndEx), true);
                }
                return null;
            }
        }
 
        #endregion ProxySimple Interface
 
        #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)
        {
            if (child._item == (int) ComboChildren.List && !IsSimpleCombo ())
            {
                return CreateComboButton ();
            }
 
            return null;
        }
 
        // Returns the previous sibling element in the raw hierarchy.
        // Peripheral controls have always negative values.
        // Returns null is no previous
        internal override ProxySimple GetPreviousSibling (ProxySimple child)
        {
            if (child._item == (int) ComboChildren.Button)
            {
                return CreateListBox ();
            }
 
            return null;
        }
 
        // Returns the first child element in the raw hierarchy.
        internal override ProxySimple GetFirstChild ()
        {
            // List portion is a first child of the combo-box
            return CreateListBox ();
        }
 
        // Returns the last child element in the raw hierarchy.
        internal override ProxySimple GetLastChild ()
        {
            if (IsSimpleCombo ())
            {
                // there is no DropDown button
                return CreateListBox ();
            }
 
            return CreateComboButton ();
        }
 
        // Returns a Proxy element corresponding to the specified screen coordinates.
        internal override ProxySimple ElementProviderFromPoint (int x, int y)
        {
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (GetComboInfo(_hwnd, ref cbInfo))
            {
                // check for button
                if (Misc.MapWindowPoints(_hwnd, IntPtr.Zero, ref cbInfo.rcButton, 2))
                {
                    if (Misc.PtInRect(ref cbInfo.rcButton, x, y))
                    {
                        return new WindowsComboButton(_hwnd, this, (int)ComboChildren.Button);
                    }
                }
 
                // check for list
                if (SafeNativeMethods.IsWindowVisible(cbInfo.hwndList))
                {
                    NativeMethods.Win32Rect rcList = NativeMethods.Win32Rect.Empty;
 
                    if (Misc.GetWindowRect(cbInfo.hwndList, ref rcList) &&
                        Misc.PtInRect(ref rcList, x, y))
                    {
                        ProxySimple el = CreateListBox(cbInfo.hwndList);
 
                        return ((WindowsListBox) el).ElementProviderFromPoint (x, y);
                    }
                }
            }
 
            return this;
        }
 
        #endregion
 
        #region ProxyHwnd Interface
 
        // override the default implementation so we can handle the WinEvents that are send to the edit
        // portion of ComboBox (Combo proxy will hide edit portion from the user, but will take care of all
        // the features/functionality that Edit presents) and some(show, hide) events that are send to the List portion of combo
        // In both cases this events will be presented to the user as Combo's LE events
        internal override void AdviseEventAdded (AutomationEvent eventId, AutomationProperty [] aidProps)
        {
            // call the base class implementation first to add combo-specific things and register combo specific callback
            base.AdviseEventAdded (eventId, aidProps);
 
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (GetComboInfo(_hwnd, ref cbInfo))
            {
                if (eventId == AutomationElement.AutomationPropertyChangedEvent)
                {
                    // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST are still editable.
                    if (cbInfo.hwndItem != IntPtr.Zero && IsEditableCombo())
                    {
                        // subscribe to edit-specific notifications, that would be presented as combo le event
                        // ValueAsString, ValueAsObject, IsReadOnly
                        // create array containing events that user is interested in
                        WinEventTracker.EvtIdProperty [] editPortionEvents;
                        int counter;
 
                        CreateEditPortionEvents (out editPortionEvents, out counter, aidProps);
                        if ( counter > 0 )
                        {
                            WinEventTracker.AddToNotificationList( cbInfo.hwndItem, new WinEventTracker.ProxyRaiseEvents( EditPortionEvents ), editPortionEvents, counter );
                        }
                    }
                }
 
                // Need to also advise the list portions of the combobox so that it can raise events.
                if (cbInfo.hwndList != IntPtr.Zero)
                {
                    WindowsListBox listbox = new WindowsListBox(cbInfo.hwndList, this, 0, true);
                    listbox.AdviseEventAdded(eventId, aidProps);
                }
            }
        }
 
        internal override void AdviseEventRemoved (AutomationEvent eventId, AutomationProperty [] aidProps)
        {
            // remove combo-related events
            base.AdviseEventRemoved (eventId, aidProps);
 
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (GetComboInfo(_hwnd, ref cbInfo))
            {
                // remove edit and list specific events that got remapped into combo's events
                if (eventId == AutomationElement.AutomationPropertyChangedEvent)
                {
                    // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST are still editable.
                    if (cbInfo.hwndItem != IntPtr.Zero && IsEditableCombo())
                    {
                        // un-subscribe from edit-specific notifications
                        // ValueAsString, ValueAsObject, IsReadOnly
                        // create array containing events from which user wants to unsubscribe
                        WinEventTracker.EvtIdProperty [] editPortionEvents;
                        int counter;
 
                        CreateEditPortionEvents (out editPortionEvents, out counter, aidProps);
                        if ( counter > 0 )
                        {
                            WinEventTracker.RemoveToNotificationList( cbInfo.hwndItem, editPortionEvents, null, counter );
                        }
                    }
                }
 
                // Need to also remove the advise from the list portions of the combobox.
                if (cbInfo.hwndList != IntPtr.Zero)
                {
                    WindowsListBox listbox = new WindowsListBox(cbInfo.hwndList, this, 0, true);
                    listbox.AdviseEventRemoved(eventId, aidProps);
                }
            }
        }
 
        #endregion
 
        #region Value Pattern
 
        // Sets the text of the edit part of the Combo
        void IValueProvider.SetValue (string str)
        {
            // Ensure that the window and all its parents are enabled.
            Misc.CheckEnabled(_hwnd);
 
            // piggy-back on win32editbox proxy
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (GetComboInfo(_hwnd, ref cbInfo) && SafeNativeMethods.IsWindowVisible(cbInfo.hwndItem))
            {
                WindowsEditBox editBox = new WindowsEditBox(cbInfo.hwndItem, null, -1);
                IValueProvider valueProvider = (IValueProvider) editBox.GetPatternProvider (ValuePattern.Pattern);
 
                // try to set user-provided text
                valueProvider.SetValue (str);
 
                // Let the parent know that the value has change, to allow the parent to do any processing it needs
                // to do on value change.
                IntPtr hwndParent = Misc.GetParent(_hwnd);
                if (hwndParent != IntPtr.Zero)
                {
                    int id = Misc.GetWindowId(_hwnd);
                    IntPtr wParam = new IntPtr(NativeMethods.Util.MAKELONG(id, NativeMethods.CBN_EDITUPDATE));
 
                    Misc.ProxySendMessage(hwndParent, NativeMethods.WM_COMMAND, wParam, _hwnd);
                }
 
                return;
            }
 
            throw new InvalidOperationException(SR.OperationCannotBePerformed);
        }
 
        // Request to get the value that this UI element is representing as a string
        string IValueProvider.Value
        {
            get
            {
                return Text;
            }
        }
 
        bool IValueProvider.IsReadOnly
        {
            get
            {
                if (!SafeNativeMethods.IsWindowEnabled(_hwnd))
                {
                    return true;
                }
 
                // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST are still editable.
                if (IsComboBoxEx32())
                {
                    return false;
                }
                else
                {
                    return IsDropDownListCombo();
                }
            }
        }
 
 
        #endregion ValuePattern
 
        #region ExpandCollapse Pattern
 
        void IExpandCollapseProvider.Expand ()
        {
            // Ensure that the window and all its parents are enabled.
            Misc.CheckEnabled(_hwnd);
 
            if (GetDroppedState (_hwnd))
            {
                // list portion is already visible
                return;
            }
 
            Expand (_hwnd);
        }
 
        void IExpandCollapseProvider.Collapse ()
        {
            // Ensure that the window and all its parents are enabled.
            Misc.CheckEnabled(_hwnd);
 
            if (!GetDroppedState (_hwnd))
            {
                // list portion is already collapsed
                return;
            }
 
            Collapse(_hwnd);
        }
 
        ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState
        {
            get
            {
                return (GetDroppedState (_hwnd)) ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed;
            }
        }
 
        #endregion ExpandCollapse Pattern
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        internal bool IsSimpleCombo ()
        {
            return (NativeMethods.CBS_SIMPLE == _comboType);
        }
 
        // no edit portion....
        internal bool IsDropDownListCombo ()
        {
            return (NativeMethods.CBS_DROPDOWNLIST == _comboType);
        }
 
        // get text of the listboxitem (external use)
        internal string GetListItemText (int index)
        {
            IntPtr hwndToAsk = IsComboBoxEx32() ? _hwndEx : _hwnd;
 
            return SpecialText (hwndToAsk, index);
        }
 
        // Detect if our combobox is hosted inside of comboex
        // This is important to know becuase:
        // Real styles will be provided by comboex
        // comboex supplies the edit
        static internal IntPtr HostedByComboEx (IntPtr hwnd)
        {
            IntPtr hwndEx = NativeMethodsSetLastError.GetAncestor (hwnd, NativeMethods.GA_PARENT);
 
            if ((IntPtr.Zero != hwndEx) && IsComboEx (hwndEx))
            {
                return hwndEx;
            }
 
            return IntPtr.Zero;
        }
 
        // Wrapper on top of Win32's GetComboInfo
        static internal bool GetComboInfo(IntPtr hwnd, ref NativeMethods.COMBOBOXINFO cbInfo)
        {
            bool result = Misc.GetComboBoxInfo(hwnd, ref cbInfo);
 
            if (result)
            {
                // some combo boxes do not have an edit portion
                // instead they return combo hwnd in the item
                // to make our life easier  set hwndItem to IntPtr.Zero
                if (cbInfo.hwndItem == cbInfo.hwndCombo)
                {
                    cbInfo.hwndItem = IntPtr.Zero;
                }
 
                // Possible that Combo is hosted by ComboboxEx32
                // hence GetComboBoxInfo did not provide us with edit.
                // We should try to detect it ourselves
                if (cbInfo.hwndItem == IntPtr.Zero && IsComboEx (NativeMethodsSetLastError.GetAncestor (hwnd, NativeMethods.GA_PARENT)))
                {
                    cbInfo.hwndItem = Misc.FindWindowEx(hwnd, IntPtr.Zero, "EDIT", null);
                    if (cbInfo.hwndItem != IntPtr.Zero)
                    {
                        result = Misc.GetWindowRect(cbInfo.hwndItem, ref cbInfo.rcItem);
                        if( result)
                        {
                            result = Misc.MapWindowPoints(_hwndDesktop, hwnd, ref cbInfo.rcItem, 2);
                        }
 
                        if (!result)
                        {
                            cbInfo.rcItem = NativeMethods.Win32Rect.Empty;
                        }
                    }
                }
            }
 
            return result;
        }
        // determin if the list portion of combo is dropped
        static internal bool GetDroppedState (IntPtr hwnd)
        {
            return Misc.ProxySendMessageInt(hwnd, NativeMethods.CB_GETDROPPEDSTATE, IntPtr.Zero, IntPtr.Zero) != 0;
        }
        // expand the list portion
        static internal void Expand (IntPtr hwnd)
        {
            IntPtr hwndFocused = Misc.GetFocusedWindow();
 
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
            GetComboInfo(hwnd, ref cbInfo);
 
            // if the combobox does not already have focus, set the focus.
            if (hwndFocused != hwnd && hwndFocused != cbInfo.hwndCombo && hwndFocused != cbInfo.hwndItem && hwndFocused != cbInfo.hwndList)
            {
                Misc.SetFocus(hwnd);
            }
            Misc.ProxySendMessage(hwnd, NativeMethods.CB_SHOWDROPDOWN, new IntPtr(1), IntPtr.Zero);
        }
        // collapse the list portion
        static internal void Collapse (IntPtr hwnd)
        {
            Misc.ProxySendMessage(hwnd, NativeMethods.CB_SHOWDROPDOWN, new IntPtr(0), IntPtr.Zero);
        }
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Internal Fields
        //
        //------------------------------------------------------
 
        #region Internal Fields
 
        // enum describing children of the combobox
        internal enum ComboChildren: int
        {
            Button = -1,
            List = 2
        }
 
        internal const string Combobox = "ComboBox";
 
        #endregion Internal Fields
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
        // retrieve combobox text
        private string Text
        {
            get
            {
                if (IsDropDownListCombo() && IsComboBoxEx32())
                {
                    // special case ComboboxEx droplist
                    string text = SpecialText(_hwndEx, -1);
                    if (!string.IsNullOrEmpty(text))
                    {
                        return text;
                    }
                }
 
                // all other cases
                return Misc.ProxyGetText(IsComboBoxEx32() ? _hwndEx : _hwnd);
            }
        }
 
        private int GetComboType()
        {
            IntPtr hwnd = IsComboBoxEx32() ? _hwndEx : _hwnd;
 
            return (Misc.GetWindowStyle(hwnd) & NativeMethods.CBS_COMBOTYPEMASK);
        }
 
        // create combo button
        private ProxySimple CreateComboButton ()
        {
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (GetComboInfo(_hwnd, ref cbInfo) && cbInfo.stateButton != NativeMethods.STATE_SYSTEM_INVISIBLE)
            {
                return new WindowsComboButton (_hwnd, this, (int) ComboChildren.Button);
            }
 
            return null;
        }
 
        // create list portion of combo box
        private ProxySimple CreateListBox()
        {
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
            if (GetComboInfo(_hwnd, ref cbInfo) && (IntPtr.Zero != cbInfo.hwndList))
            {
                return new WindowsListBox(cbInfo.hwndList, this, (int)ComboChildren.List, true);
            }
            return null;
        }
 
        // create listbox from known hwnd
        private ProxySimple CreateListBox (IntPtr hwndList)
        {
            return new WindowsListBox(hwndList, this, (int)ComboChildren.List, true);
        }
 
        // detect if passed int window corresponds to the comboex
        static private bool IsComboEx (IntPtr hwndEx)
        {
            if (hwndEx == IntPtr.Zero)
            {
                return false;
            }
 
            return string.Equals(Misc.GetClassName(hwndEx), ComboboxEx32, StringComparison.OrdinalIgnoreCase);
        }
 
        private bool IsEditableCombo()
        {
            // Standard Combo Box controls with the style CBS_DROPDOWNLIST has the edit control replaced by a
            // static text item to displays the current selection in the list box.  This is not true of
            // ComboBoxEx32 controls with the style CBS_DROPDOWNLIST.
            return !IsDropDownListCombo() || IsComboBoxEx32();
        }
 
        // Retrieve the text of the list portion of Combo.
        // Or Text of the edit portion of ComboBoxEx32 (path -1 as index)
        // Use CB_XXX instead of LB_XXX, since CB_XXX will give us back text in ownerdrawn combo
        static private string SpecialText (IntPtr hwnd, int index)
        {
            if (index == -1)
            {
                // get the selected element
                index = Misc.ProxySendMessageInt(hwnd, NativeMethods.CB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);
                if (index == -1)
                {
                    return "";
                }
            }
 
            int len = Misc.ProxySendMessageInt(hwnd, NativeMethods.CB_GETLBTEXTLEN, new IntPtr(index), IntPtr.Zero);
 
            if (len < 1)
            {
                return "";
            }
 
            if (Misc.GetClassName(hwnd).Equals("Internet Explorer_TridentCmboBx"))
            {
                // The Trident listbox is a superclassed standard listbox.
                // Trident listboxes are owner draw that does not have the hasstring style set.
                // All the control contains is the owner draw data and not text.  Private
                // messages were added to retrieve the owner draw data as text.  The new messages
                // are used just like the normally LB_GETTEXT and CB_GETTEXT messages.
                return XSendMessage.GetItemText(hwnd, NativeMethods.WM_USER + NativeMethods.CB_GETLBTEXT, index, len);
            }
            else
            {
                return Misc.GetUnsafeText(hwnd, NativeMethods.CB_GETLBTEXT, new IntPtr(index), len);
            }
        }
 
        // Combo-specific events
        static private void RaiseEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            // ------------------------------------------------------/////////////////////////////////////
            //
            // Depending of the type of Combobox we will get different WinEvents
            //
            // Simple: No events
            //
            // DropDown: OBJ_STATECHANGE: for the dropbutton button (idChild == 2)
            // NOTE: that OBJECT_STATECHANGE will only be generated when user uses button to show the list
            // if user uses the button to hide the list, event will not be generated
            //
            // DropDownList: OBJ_STATECHANGE (same as above)
            //             : OBJECT_VALUECHANGE - when using the keyboard to navigate between list children
            //                                  - no need to handle it here, ListBox proxy will take care of that
            //
            // ------------------------------------------------------//////////////////////////////////////
            ProxySimple el = null;
 
            if (idProp is AutomationProperty && idProp == ExpandCollapsePattern.ExpandCollapseStateProperty)
            {
                // expand/collapse events are handled in WindowsListBox with the ComboLBox hwnd.
                // so just return here so we don't fire extraneous events
                return;
            }
 
            switch (idObject)
            {
                case NativeMethods.OBJID_CLIENT :
                    {
                        if (eventId == NativeMethods.EventObjectStateChange && idChild == 2)
                        {
                            // event came for combobutton
                            // We will be getting 2 OBJECT_STATECHANGED event
                            // one with button state pressed and another normal
                            // both indicate the same invoke event
                            // hence second event is a duplicate of the first one and we need to filter it out
                            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
                            if (WindowsComboBox.GetComboInfo(hwnd, ref cbInfo) && Misc.IsBitSet(NativeMethods.STATE_SYSTEM_PRESSED, cbInfo.stateButton))
                            {
                                // The event could be for both the button and the combo
                                WindowsComboBox cb = (WindowsComboBox) Create (hwnd, 0);
                                cb.DispatchEvents (eventId, idProp, idObject, idChild);
 
                                el = cb.CreateComboButton ();
                                el.DispatchEvents (eventId, idProp, idObject, idChild);
                                return;
                            }
                        }
                        el = (ProxySimple) Create (hwnd, 0);
                        break;
                    }
 
            }
 
            if (el != null)
            {
                el.DispatchEvents (eventId, idProp, idObject, idChild);
            }
        }
 
        // Handles combo's edit portion specific events
        // as combo-specific events
        private static void EditPortionEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            // Get hwnd of the combo-box
            IntPtr hwndCombo = NativeMethodsSetLastError.GetAncestor (hwnd, NativeMethods.GA_PARENT);
 
            if (hwndCombo != IntPtr.Zero)
            {
                ProxySimple el = (ProxySimple) Create (hwndCombo, 0);
 
                el.DispatchEvents (eventId, idProp, idObject, idChild);
            }
        }
 
        // Return an array that contains combo's edit portion specific events
        // These events will be remapped as combo box events
        private static void CreateEditPortionEvents (out WinEventTracker.EvtIdProperty [] editPortionEvents, out int counter, AutomationProperty [] aidProps)
        {
            // count how many events to pass back for the edit part of combo
            int c = 0;
            foreach ( AutomationProperty p in aidProps )
            {
                if ( p == ValuePattern.ValueProperty || p == ValuePattern.IsReadOnlyProperty )
                {
                    c++;
                }
            }
 
            if (c == 0)
            {
                editPortionEvents = null;
                counter = 0;
                return;
            }
 
            // allocate array with the number of events from above
            editPortionEvents = new WinEventTracker.EvtIdProperty[c];
 
            c = 0;
            foreach ( AutomationProperty p in aidProps )
            {
                if ( p == ValuePattern.ValueProperty || p == ValuePattern.IsReadOnlyProperty )
                {
                    editPortionEvents[c]._idProp = p;
                    editPortionEvents[c]._evtId = (p == ValuePattern.ValueProperty) ? NativeMethods.EventObjectValueChange : NativeMethods.EventObjectStateChange;
                    c++;
                }
            }
 
            counter = c;
        }
 
        // When _hwndEx is not IntPtr.Zero the control is a ComboBoxEx32 control.
        private bool IsComboBoxEx32()
        {
            return _hwndEx != IntPtr.Zero;
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private fields
 
        // HWND of ComboBoxEx32 (if exist)
        private IntPtr _hwndEx;
 
        private int _comboType;
 
        private const string ComboboxEx32 = "ComboBoxEx32";
 
        #endregion Private fields
 
        //------------------------------------------------------
        //
        //  WindowsComboButton Private Class
        //
        //------------------------------------------------------
 
        #region WindowsComboButton
 
        // Proxy for ComboBox button
        class WindowsComboButton: ProxySimple, IInvokeProvider
        {
            //------------------------------------------------------
            //
            //  Constructors
            //
            //------------------------------------------------------
 
            #region Constructor
 
            internal WindowsComboButton (IntPtr hwnd, ProxyFragment parent, int item)
            : base(hwnd, parent, item)
            {
                _cControlType = ControlType.Button;
                _sAutomationId = "DropDown"; // This string is a non-localizable string
            }
 
            #endregion Constructor
 
            //------------------------------------------------------
            //
            //  Pattern Implementation
            //
            //------------------------------------------------------
 
            #region ProxySimple Interface
 
            // Returns a pattern interface if supported.
            internal override object GetPatternProvider (AutomationPattern iid)
            {
                if (iid == InvokePattern.Pattern)
                {
                    return this;
                }
 
                return null;
            }
 
            // Gets the bounding rectangle for this element
            internal override Rect BoundingRectangle
            {
                get
                {
                    NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
                    if (!WindowsComboBox.GetComboInfo(_hwnd, ref cbInfo))
                    {
                        return Rect.Empty;
                    }
                    if (!Misc.MapWindowPoints(_hwnd, IntPtr.Zero, ref cbInfo.rcButton, 2))
                    {
                        return Rect.Empty;
                    }
 
                    // Don't need to normalize, MapWindowPoints returns absolute coordinates.
                    return cbInfo.rcButton.ToRect(false);
                }
            }
 
            //Gets the localized name
            internal override string LocalizedName
            {
                get
                {
                    return SR.LocalizedNameWindowsComboButton;
                }
            }
 
            #endregion ProxySimple Interface
 
            #region Invoke Pattern
 
            // Same effect as a click on the drop down button
            void IInvokeProvider.Invoke ()
            {
                // Ensure that the window and all its parents are enabled.
                Misc.CheckEnabled(_hwnd);
 
                if (!WindowsComboBox.GetDroppedState (_hwnd))
                {
                    WindowsComboBox.Expand (_hwnd);
                }
                else
                {
                    WindowsComboBox.Collapse (_hwnd);
                }
            }
 
            #endregion Invoke Pattern
        }
 
        #endregion
 
    }
}