File: MS\Internal\AutomationProxies\WindowsEditBox.cs
Project: wpf\src\UIAutomation\Win32Providers\UIAutomationClientsideProviders.csproj (UIAutomationClientsideProviders)
//---------------------------------------------------------------------------
//
// <copyright file=WindowsEditBox.cs company=Microsoft>
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
//
// Description: HWND-based Edit Box proxy
//
// History:
//  01/10/03 : a-jeanp - created
//  08/12/03:  alexsn - bug fixes
//  03/02/04:  a-davidj - added text pattern
//
//---------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text;
using MS.Win32;
using NativeMethodsSetLastError = MS.Internal.UIAutomationClientSideProviders.NativeMethodsSetLastError;
 
namespace MS.Internal.AutomationProxies
{
    // TERMINOLOGY: Win32 Edit controls use the term "index" to mean a character position.
    // For example the EM_LINEINDEX message converts a line number to it's starting character position.
    // Perhaps not the best choice but we use it to be consistent.
 
    class WindowsEditBox : ProxyHwnd, IValueProvider, ITextProvider
    {
        // ------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
 
        #region Constructors
 
        // This is the default contructor that calls the base class constructor.
        // This function determines the type of the edit control and then calls the default constructor.
        internal WindowsEditBox (IntPtr hwnd, ProxyFragment parent, int item)
            : base( hwnd, parent, item)
        {
            _type = GetEditboxtype (hwnd);
            if (IsMultiline)
            {
                _cControlType = ControlType.Document;
            }
            else
            {
                _cControlType = ControlType.Edit;
            }
 
            // When embedded inside of the combobox, hide the edit portion in the content element tree
            _fIsContent = !IsInsideOfCombo();
            _fIsKeyboardFocusable = true;
 
            if (IsInsideOfListView(hwnd))
            {
                _sAutomationId = "edit";
            }
 
            // support for events
            _createOnEvent = new WinEventTracker.ProxyRaiseEvents (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);
        }
 
        private static IRawElementProviderSimple Create(IntPtr hwnd, int idChild)
        {
            // @
 
 
 
 
 
 
 
            if (idChild != 0)
            {
                System.Diagnostics.Debug.Assert (idChild == 0, "Invalid Child Id, idChild != 0");
                throw new ArgumentOutOfRangeException("idChild", idChild, SR.Get(SRID.ShouldBeZero));
            }
 
            return new WindowsEditBox(hwnd, null, 0);
        }
 
        // Static Create method called by the event tracker system
        internal static void RaiseEvents (IntPtr hwnd, int eventId, object idProp, int idObject, int idChild)
        {
            if (idObject != NativeMethods.OBJID_VSCROLL && idObject != NativeMethods.OBJID_HSCROLL)
            {
                ProxySimple el;
                if (IsInsideOfIPAddress(hwnd))
                {
                    el = new ByteEditBoxOverride(hwnd, idChild);
                }
                else
                {
                    el = new WindowsEditBox(hwnd, null, 0);
 
                    // If this is an Edit control inside of a Microsoft Spinner, need to treat the property
                    // changes for as property changes on the whole Microsoft Spinner, not on the element
                    // of the Microsoft Spinner.  Microsoft Spinner raise WinEvents on the Edit portion of
                    // the spinner and not the UpDown portion like the Win32 Spinner.
                    IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(hwnd, NativeMethods.GA_PARENT);
                    if (hwndParent != IntPtr.Zero)
                    {
                        // Test for spinner - Create checks if the element is a spinner
                        ProxySimple spinner = (ProxySimple)WinformsSpinner.Create(hwndParent, 0);
                        if (spinner != null)
                        {
                            el = spinner;
                        }
                    }
                }
 
                el.DispatchEvents (eventId, idProp, idObject, idChild);
            }
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Patterns Implementation
        //
        //------------------------------------------------------
 
        #region ProxySimple Interface
 
        internal override string GetAccessKey()
        {
            string accessKey = base.GetAccessKey();
            if ((bool)GetElementProperty(AutomationElement.IsKeyboardFocusableProperty))
            {
                // Walk up the parent hwnd chain to find the associated label,
                // e.g. for the case of an edit box in a writable combo box.
                // The hwnd tree in this case may look like:
                //    - Dialog
                //      |- ComboBoxEx32  (optional)
                //         |- ComboBox
                //            |- Edit
                // So we may need to walk up several levels.
                // Although this loop isn't really confined to a window hierarchy
                // of exactly the type shown above, in practice there are no realistic
                // situations where this will do any harm.   If this is shown to be
                // a problem at some point, we will have to constrain the loop to
                // parent hwnd's of type ComboBox or ComboBoxEx32.
                for (IntPtr hwnd = _hwnd;
                            hwnd != IntPtr.Zero && string.IsNullOrEmpty(accessKey);
                            hwnd = Misc.GetParent(hwnd))
                {
                    accessKey = GetLabelAccessKey(hwnd);
                }
            }
            return accessKey;
        }
 
        // Returns a pattern interface if supported.
        internal override object GetPatternProvider(AutomationPattern iid)
        {
            if (IsInsideOfIPAddress(_hwnd))
            {
                // ByteEditBoxOverride will service this call.
                return null;
            }
            else if (iid == ValuePattern.Pattern && !IsMultiline)
            {
                return this;
            }
            // text pattern is supported for non-password boxes
            else if (iid == TextPattern.Pattern && _type != EditboxType.Password)
            {
                return this;
            }
 
            return null;
        }
 
        // Process all the Logical and Raw Element Properties
        internal override object GetElementProperty(AutomationProperty idProp)
        {
            if (idProp == AutomationElement.IsControlElementProperty)
            {
                // When embedded inside of the spinner, hide edit portion in the logical tree
                if (IsInsideOfSpinner())
                {
                    return false;
                }
            }
            else if (idProp == AutomationElement.IsPasswordProperty)
            {
                return _type == EditboxType.Password;
            }
            else if (idProp == AutomationElement.NameProperty)
            {
                // Per ControlType.Edit spec if there is no static text
                // label for an Edit then it must NEVER return the contents
                // of the edit as the Name property.  This relies on the
                // default ProxyHwnd impl looking for a label for Name and
                // not using WM_GETTEXT on the edit hwnd.
                string name = base.GetElementProperty(idProp) as string;
                if (string.IsNullOrEmpty(name))
                {
                    // Stop UIA from asking other providers
                    return AutomationElement.NotSupported;
                }
 
                return name;
            }
 
            return base.GetElementProperty(idProp);
        }
 
        internal override ProxySimple ElementProviderFromPoint(int x, int y)
        {
            IntPtr hwndUpDown = WindowsSpinner.GetUpDownFromEdit(_hwnd);
            if (hwndUpDown != IntPtr.Zero)
            {
                return new WindowsSpinner(hwndUpDown, _hwnd, _parent, _item);
            }
 
            return base.ElementProviderFromPoint(x, y);
        }
 
        internal override ProxySimple GetFocus()
        {
            IntPtr hwndUpDown = WindowsSpinner.GetUpDownFromEdit(_hwnd);
            if (hwndUpDown != IntPtr.Zero)
            {
                return new WindowsSpinner(hwndUpDown, _hwnd, _parent, _item);
            }
 
            return base.GetFocus();
        }
 
        internal override ProxySimple GetParent()
        {
            IntPtr hwndUpDown = WindowsSpinner.GetUpDownFromEdit(_hwnd);
            if (hwndUpDown != IntPtr.Zero)
            {
                return new WindowsSpinner(hwndUpDown, _hwnd, _parent, _item);
            }
 
            return base.GetParent();
        }
 
        #endregion ProxySimple Interface
 
        #region ProxyHwnd Overrides
 
        // Builds a list of Win32 WinEvents to process a UIAutomation Event.
        protected override WinEventTracker.EvtIdProperty[] EventToWinEvent(AutomationEvent idEvent, out int cEvent)
        {
            // Once the shell team implements the WinEvent EVENT_OBJECT_TEXTSELECTIONCHANGED,
            // we will start to see 2 events fired when the text selection changes.
            // At that point, uncomment the code below so that
            // TextPattern.TextSelectionChanged will only fire on Vista
            // in response to NativeMethods.EventObjectTextSelectionChanged,
            // but not in response to NativeMethods.EventObjectLocationChange.
 
            /*
            // For Vista, we only need register for EventObjectTextSelectionChanged to handle
            // TextPattern.TextSelectionChangedEvent.
            // For XP, we rely on EventObjectLocationChange, handled in ProxyHwnd.EventToWinEvent().
            if (idEvent == TextPattern.TextSelectionChangedEvent && Environment.OSVersion.Version.Major >= 6)
            {
                cEvent = 1;
                return new WinEventTracker.EvtIdProperty[] {
                    new WinEventTracker.EvtIdProperty (NativeMethods.EventObjectTextSelectionChanged, idEvent)
                };
            }
            */
 
            return base.EventToWinEvent(idEvent, out cEvent);
        }
 
        #endregion
 
        #region Value Pattern
 
        // Sets the text of the edit.
        void IValueProvider.SetValue (string str)
        {
            // Ensure that the edit box and all its parents are enabled.
            Misc.CheckEnabled(_hwnd);
 
            int styles = WindowStyle;
 
            if (Misc.IsBitSet(styles, NativeMethods.ES_READONLY))
            {
                throw new InvalidOperationException(SR.Get(SRID.ValueReadonly));
            }
 
            // check if control only accepts numbers
            if (Misc.IsBitSet(styles, NativeMethods.ES_NUMBER))
            {
                // check if string contains any non-numeric characters.
                foreach (char ch in str)
                {
                    if (char.IsLetter (ch))
                    {
                        throw new ArgumentException(SR.Get(SRID.NotAValidValue, str), "val");
                    }
                }
            }
 
            // bug# 11147, Text/edit box should not enter more characters than what is allowed through keyboard.
            // Determine the max number of chars this editbox accepts
 
            int result = Misc.ProxySendMessageInt(_hwnd, NativeMethods.EM_GETLIMITTEXT, IntPtr.Zero, IntPtr.Zero);
            // A result of -1 means that no limit is set.
            if (result != -1 && result < str.Length)
            {
                throw new InvalidOperationException (SR.Get(SRID.OperationCannotBePerformed));
            }
 
            // Send the message...
            result = Misc.ProxySendMessageInt(_hwnd, NativeMethods.WM_SETTEXT, IntPtr.Zero, new StringBuilder(str));
            if (result != 1)
            {
                throw new InvalidOperationException(SR.Get(SRID.OperationCannotBePerformed));
            }
        }
 
        // Request to get the value that this UI element is representing as a string
        string IValueProvider.Value
        {
            get
            {
                return GetValue();
            }
        }
 
        bool IValueProvider.IsReadOnly
        {
            get
            {
                return IsReadOnly();
            }
        }
 
        #endregion
 
        #region Text Pattern Methods
 
        ITextRangeProvider [] ITextProvider.GetSelection()
        {
            int start, end;
            GetSel(out start, out end);
            return new ITextRangeProvider[] { new WindowsEditBoxRange(this, start, end) };
        }
 
        ITextRangeProvider [] ITextProvider.GetVisibleRanges()
        {
            int start, end;
            GetVisibleRangePoints(out start, out end);
 
            return new ITextRangeProvider[] { new WindowsEditBoxRange(this, start, end) };
        }
 
        ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
        {
            // we don't have any children so this call must be in error.
            throw new InvalidOperationException(SR.Get(SRID.EditControlsHaveNoChildren,GetType().FullName));
        }
 
        ITextRangeProvider ITextProvider.RangeFromPoint(Point screenLocation)
        {
            // convert screen to client coordinates.
            // (Essentially ScreenToClient but MapWindowPoints accounts for window mirroring using WS_EX_LAYOUTRTL.)
            NativeMethods.Win32Point clientLocation = (NativeMethods.Win32Point)screenLocation;
            if (!Misc.MapWindowPoints(IntPtr.Zero, WindowHandle, ref clientLocation, 1))
            {
                return null;
            }
 
            // we have to deal with the possibility that the coordinate is inside the window rect
            // but outside the client rect. in that case we just scoot it over so it is at the nearest
            // point in the client rect.
            NativeMethods.Win32Rect clientRect = new NativeMethods.Win32Rect();
            if (!Misc.GetClientRect(WindowHandle, ref clientRect))
            {
                return null;
            }
            clientLocation.x = Math.Max(clientLocation.x, clientRect.left);
            clientLocation.x = Math.Min(clientLocation.x, clientRect.right);
            clientLocation.y = Math.Max(clientLocation.y, clientRect.top);
            clientLocation.y = Math.Min(clientLocation.y, clientRect.bottom);
 
            // get the character at those client coordinates
            int start = CharFromPosEx(clientLocation);
            return new WindowsEditBoxRange(this, start, start);
        }
 
        #endregion TextPattern Methods
 
        #region TextPattern Properties
 
        ITextRangeProvider ITextProvider.DocumentRange
        {
            get
            {
                return new WindowsEditBoxRange(this, 0, GetTextLength());
            }
        }
 
        // THESE PROPERTIES ARE REMOVED IN 8.2 BREAKING CHANGE.
        // 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
        SupportedTextSelection ITextProvider.SupportedTextSelection
        {
            get
            {
                return SupportedTextSelection.Single;
            }
        }
 
        #endregion Text Properties
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        internal bool IsMultiline
        {
            get
            {
                return _type == EditboxType.Multiline;
            }
        }
 
        internal bool IsScrollable
        {
            get
            {
                return _type == EditboxType.Scrollable;
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
 
        // send an EM_CHARPOS message with an (x,y) coordinate in lParam and
        // report the returned character index and line number.
        // IMPORTANT: the character index and line number are each 16-bit (half of the lResult).
        // the high-order word is zero.
        internal void CharFromPos(NativeMethods.Win32Point point, out ushort indexLowWord, out ushort lineLowWord)
        {
            Debug.Assert(point.x >= 0 && point.x < 65536, "WindowsEditBox.CharFromPos() x coordinate out of range.");
            Debug.Assert(point.y >= 0 && point.y < 65536, "WindowsEditBox.CharFromPos() y coordinate out of range.");
 
            // ask edit control for line number and character offset at client coordinates.
            // low order word of return value is the character position.
            // high order word is the zero-based line index.
            IntPtr lParam = NativeMethods.Util.MAKELPARAM(point.x, point.y);
            int result = Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_CHARFROMPOS, IntPtr.Zero, lParam);
            indexLowWord = unchecked((ushort)(NativeMethods.Util.LOWORD(result)));
            lineLowWord = unchecked((ushort)(NativeMethods.Util.HIWORD(result)));
        }
 
        // an improvement on EM_CHARFROMPOS that handles edit controls with content greater than 65535 characters.
        internal int CharFromPosEx(NativeMethods.Win32Point point)
        {
            // get the low words of the character position and line number at the coordinate.
            ushort indexLowWord, lineLowWord;
            CharFromPos(point, out indexLowWord, out lineLowWord);
 
            // we handle multi-line edit controls differently than single line edit controls.
            int index;
            if (IsMultiline)
            {
                // note: an optimization would be to call GetTextLength and do the following only if the length is
                // greater than 65535.
 
                // to make the line number accurate in the case there are more than 65535 lines we get a 32-bit line number
                // for the first visible line then use it to figure out the high-order word of our line number.
                // this assumes that there aren't more than 65535 visible lines in the window, which is a
                // pretty safe assumption.
                int line = IntFromLowWord(lineLowWord, GetFirstVisibleLine());
 
                // to make the character position accurate in case there are more than 65535 characters we use our line number to
                // get the character position of the beginning of that line and use it to figure out the high-order word
                // of our character position.
                // this assumes that there aren't more than 65535 characters on the line before our target character,
                // which is a pretty safe assumption.
                index = IntFromLowWord(indexLowWord, LineIndex(line));
            }
            else
            {
                // single-line edit control
 
                // to make the character position accurate in case there are more than 65535 characters we get a 32-bit
                // character position from the first visible character, then use it to figure out the high-order word
                // of our character position.
                // this assumes that there aren't more than 65535 visible characters in the window,
                // which is a pretty safe assumption.
                index = IntFromLowWord(indexLowWord, GetFirstVisibleChar());
            }
            return index;
        }
 
        // Retrive edit box type
        internal static EditboxType GetEditboxtype (IntPtr hwnd)
        {
            int style = Misc.GetWindowStyle(hwnd);
            EditboxType type = EditboxType.Editbox;
 
            if (Misc.IsBitSet(style, NativeMethods.ES_PASSWORD))
            {
                type = EditboxType.Password;
            }
            else if (Misc.IsBitSet(style, NativeMethods.ES_MULTILINE))
            {
                type = EditboxType.Multiline;
            }
            else if (Misc.IsBitSet(style, NativeMethods.ES_AUTOHSCROLL))
            {
                type = EditboxType.Scrollable;
            }
 
            return type;
        }
 
        // send an EM_GETFIRSTVISIBLELINE message to a single-line edit box to get the first visible character.
        internal int GetFirstVisibleChar()
        {
            Debug.Assert(!IsMultiline);
            return Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_GETFIRSTVISIBLELINE, IntPtr.Zero, IntPtr.Zero);
        }
 
        // send an EM_GETFIRSTVISIBLELINE message to a multi-line edit box to get the first visible line.
        internal int GetFirstVisibleLine()
        {
            Debug.Assert(IsMultiline);
            return Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_GETFIRSTVISIBLELINE, IntPtr.Zero, IntPtr.Zero);
        }
 
        // send a WM_GETFONT message to find out what font the edit control is using.
        internal IntPtr GetFont()
        {
            IntPtr result = Misc.ProxySendMessage(WindowHandle, NativeMethods.WM_GETFONT, IntPtr.Zero, IntPtr.Zero);
 
            //
            // A null result is within normal bounds, as per the WM_GETFONT documentation:
            // The return value is a handle to the font used by the control, or NULL if the control is using the system font.
            //
            if (result == IntPtr.Zero)
            {
                result = UnsafeNativeMethods.GetStockObject(NativeMethods.SYSTEM_FONT);
            }
 
            return result;
        }
 
        // send an EM_GETLINECOUNT message to find out how many lines are in the edit box.
        internal int GetLineCount()
        {
            return Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_GETLINECOUNT, IntPtr.Zero, IntPtr.Zero);
        }
 
        internal NativeMethods.LOGFONT GetLogfont()
        {
            IntPtr hfont = GetFont();
            Debug.Assert(hfont != IntPtr.Zero, "WindowsEditBox.GetLogfont got null HFONT");
            NativeMethods.LOGFONT logfont = new NativeMethods.LOGFONT();
            int cb = Marshal.SizeOf(typeof(NativeMethods.LOGFONT));
            if (Misc.GetObjectW(hfont, cb, ref logfont) != cb)
            {
                Debug.Assert(false, "WindowsEditBox.GetObject unexpected return value");
            }
            return logfont;
        }
 
        // send an EM_GETRECT message to find out the bounding rectangle
        internal Rect GetRect()
        {
            NativeMethods.Win32Rect rect = new NativeMethods.Win32Rect();
            Misc.ProxySendMessage(WindowHandle, NativeMethods.EM_GETRECT, IntPtr.Zero, ref rect);
            return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
        }
 
        // send an EM_GETSEL message to get the starting and ending
        // character positions of the selection.
        internal void GetSel(out int start, out int end)
        {
            Misc.ProxySendMessage(WindowHandle, NativeMethods.EM_GETSEL, out start, out end);
        }
 
        // send a WM_GETTEXT message to get the text of the edit box.
        internal string GetText()
        {
            return Misc.ProxyGetText(_hwnd, GetTextLength());
        }
 
        // send a WM_GETTEXTLENGTH to find out how long the text is in the edit box.
        internal int GetTextLength()
        {
            return Misc.ProxySendMessageInt(WindowHandle, NativeMethods.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
        }
 
        internal void GetVisibleRangePoints(out int start, out int end)
        {
            start = 0;
            end = 0;
 
            NativeMethods.Win32Rect rect = new NativeMethods.Win32Rect();
 
            if (Misc.GetClientRect(_hwnd, ref rect) && !rect.IsEmpty)
            {
                NativeMethods.SIZE size;
                string s = new string('E', 1);
                GetTextExtentPoint32(s, out size);
 
                NativeMethods.Win32Point ptStart = new NativeMethods.Win32Point((int)(rect.left + size.cx / 4), (int)(rect.top + size.cy / 4));
                NativeMethods.Win32Point ptEnd = new NativeMethods.Win32Point((int)(rect.right - size.cx / 8), (int)(rect.bottom - size.cy / 4));
 
                start = CharFromPosEx(ptStart);
                end = CharFromPosEx(ptEnd);
 
                if (start > 0)
                {
                    Point pt = PosFromChar(start);
                    if (pt.X < rect.left)
                    {
                        start++;
                    }
                }
            }
            else
            {
                // multi-line edit controls are handled differently than single-line edit controls.
 
                if (IsMultiline)
                {
                    // get the line number of the first visible line and start the range at
                    // the beginning of that line.
                    int firstLine = GetFirstVisibleLine();
                    start = LineIndex(firstLine);
 
                    // calculate the line number of the first line scrolled off the bottom and
                    // end the range at the beginning of that line.
                    end = LineIndex(firstLine + LinesPerPage());
                }
                else
                {
                    // single-line edit control
 
                    // the problem is that using a variable-width font the number of characters visible
                    // depends on the text that is in the edit control.  so we can't just divide the
                    // width of the edit control by the width of a character.
 
                    // so instead we do a binary search of the characters from the first visible character
                    // to the end of the text to find the visibility boundary.
                    Rect r = GetRect();
                    int limit = GetTextLength();
                    start = GetFirstVisibleChar();
 
                    int lo = start; // known visible
                    int hi = limit; // known non-visible
                    while (lo + 1 < hi)
                    {
                        int mid = (lo + hi) / 2;
 
                        Point pt = PosFromChar(mid);
                        if (pt.X >= r.Left && pt.X < r.Right)
                        {
                            lo = mid;
                        }
                        else
                        {
                            hi = mid;
                        }
                    }
 
                    // trim off one character unless the range is empty or reaches the end.
                    end = hi > start && hi < limit ? hi - 1 : hi;
                }
            }
        }
 
        // return true iff the user can modify the text in the edit control.
        internal bool IsReadOnly()
        {
            return (!SafeNativeMethods.IsWindowEnabled(WindowHandle) || Misc.IsBitSet(WindowStyle, NativeMethods.ES_READONLY));
        }
 
        // send an EM_LINEFROMCHAR message to find out what line contains a character position.
        internal int LineFromChar(int index)
        {
            Debug.Assert(index >= 0, "WindowsEditBox.LineFromChar negative index.");
            Debug.Assert(index <= GetTextLength(), "WindowsEditBox.LineFromChar index out of range.");
            // The return of EM_LINEFROMCHAR is the zero-based line number of the line containing the character index.
            return Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_LINEFROMCHAR, (IntPtr)index, IntPtr.Zero);
        }
 
        // send an EM_LINEINDEX message to get the character position at the start of a line.
        internal int LineIndex(int line)
        {
            int index = Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_LINEINDEX, (IntPtr)(line), IntPtr.Zero);
            return index >=0 ? index : GetTextLength();
        }
 
        // send an EM_LINESCROLL message to scroll it horizontally and/or vertically
        internal bool LineScroll(int charactersHorizontal, int linesVertical)
        {
            return 0 != Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_LINESCROLL, (IntPtr)charactersHorizontal, (IntPtr)linesVertical);
        }
 
        // determine the number of lines that are visible in the Edit control.
        // if there is no scrollbar then this is the actual number of lines displayed.
        internal int LinesPerPage()
        {
            int linePerPage = 0;
            if (Misc.IsBitSet(WindowStyle, NativeMethods.WS_VSCROLL))
            {
                // we call GetScrollInfo and return the size of the "page"
                NativeMethods.ScrollInfo si = new NativeMethods.ScrollInfo();
                si.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(NativeMethods.ScrollInfo));
                si.fMask = NativeMethods.SIF_ALL;
                bool ok = Misc.GetScrollInfo(WindowHandle, NativeMethods.SB_VERT, ref si);
                linePerPage = ok ? si.nPage : 0;
                if (IsMultiline && linePerPage <= 0)
                {
                    linePerPage = 1;
                }
            }
            else
            {
                NativeMethods.Win32Rect rect = new NativeMethods.Win32Rect();
 
                if (Misc.GetClientRect(_hwnd, ref rect) && !rect.IsEmpty)
                {
                    NativeMethods.SIZE size;
                    string s = new string('E', 1);
                    GetTextExtentPoint32(s, out size);
 
                    if (size.cy != 0)
                    {
                        linePerPage = (rect.bottom - rect.top) / size.cy;
                    }
                }
            }
            return linePerPage;
        }
 
        // send an EM_POSFROMCHAR message to find out the (x,y) position of a character.
        // note: this assumes that the x and y coordinates will not be greater than 65535.
        internal Point PosFromChar(int index)
        {
            Debug.Assert(index >= 0, "WindowsEditBox.PosFromChar negative index.");
            Debug.Assert(index < GetTextLength(), "WindowsEditBox.PosFromChar index out of range.");
 
            int result = Misc.ProxySendMessageInt(WindowHandle, NativeMethods.EM_POSFROMCHAR, (IntPtr)index, IntPtr.Zero);
            Debug.Assert(result!=-1, "WindowsEditBox.PosFromChar index out of bounds.");
 
            // A returned coordinate can be a negative value if the specified character is not displayed in the
            // edit control's client area.  So do not loose the sign.
            int x = (int)((short)NativeMethods.Util.LOWORD(result));
            int y = (int)((short)NativeMethods.Util.HIWORD(result));
            return new Point(x, y);
        }
 
        // a variation on EM_POSFROMCHAR that returns the upper-right corner instead of upper-left.
        internal Point PosFromCharUR(int index, string text)
        {
            // get the upper-left of the character
            Point pt;
            char ch = text[index];
            switch(ch)
            {
                case '\n':
                case '\r':
                    // get the UL corner of the character and return it since these characters have no width.
                    pt = PosFromChar(index);
                    break;
 
                case '\t':
                    {
                        // for tabs the calculated width of the character is no help so we use the
                        // UL corner of the following character if it is on the same line.
                        bool useNext = index<GetTextLength()-1 && LineFromChar(index+1)==LineFromChar(index);
                        pt = PosFromChar(useNext ? index+1 : index);
                    }
                    break;
 
                default:
                    {
                        // get the UL corner of the character
                        pt = PosFromChar(index);
 
                        // add the width of the character at that position.
                        NativeMethods.SIZE size;
                        string s = new string(ch, 1);
                        if( GetTextExtentPoint32(s, out size) == 0)
                        {
                            break;
                        }
 
                        pt.X += size.cx;
                    }
                    break;
            }
            return pt;
        }
 
        // send an EM_SETSEL message to set the selection.
        internal void SetSel(int start, int end)
        {
            Debug.Assert(start >= 0, "WindowsEditBox.SetSel negative start.");
            Debug.Assert(start <= GetTextLength(), "WindowsEditBox.SetSel start out of range.");
            Debug.Assert(end >= 0, "WindowsEditBox.SetSel negative end.");
            Debug.Assert(end <= GetTextLength(), "WindowsEditBox.SetSel end out of range.");
 
            Misc.ProxySendMessage(WindowHandle, NativeMethods.EM_SETSEL, (IntPtr)start, (IntPtr)end);
        }
 
        // Retrieves text associated with EditBox control.
        // this differs from GetText(), above, in that it limits the text to 2000 characters.
        // it is used by the Value pattern methods, not the Text pattern methods.
        internal static string Text(IntPtr hwnd)
        {
            return Misc.ProxyGetText(hwnd);
        }
 
        #endregion
 
        //------------------------------------------------------
        //
        //  Internal Fields
        //
        //------------------------------------------------------
 
        #region Internal Fields
 
        // Different styles of edit boxes.
        internal enum EditboxType
        {
            Editbox,
            Multiline,
            Password,
            Readonly,
            Scrollable
        };
 
        #endregion
 
        // ------------------------------------------------------
        //
        // Private Methods
        //
        // ------------------------------------------------------
 
        #region Private methods
 
        private int GetTextExtentPoint32(string text, out NativeMethods.SIZE size)
        {
            size.cx = 0;
            size.cy = 0;
 
            // add the width of the character at that position.
            // note: if any of these can throw an exception then we should use a finally clause
            // to ensure the DC is released.
            IntPtr hdc = Misc.GetDC(_hwnd);
            if (hdc == IntPtr.Zero)
            {
                return 0;
            }
 
            IntPtr oldFont = IntPtr.Zero;
            try
            {
                IntPtr hfont = GetFont();
                oldFont = Misc.SelectObject(hdc, hfont);
                return Misc.GetTextExtentPoint32(hdc, text, text.Length, out size);
            }
            finally
            {
                if (oldFont != IntPtr.Zero)
                {
                    Misc.SelectObject(hdc, oldFont);
                }
 
                Misc.ReleaseDC(_hwnd, hdc);
            }
        }
 
        private string GetValue()
        {
            if (_type == EditboxType.Password)
            {
                // Trying to retrieve the text (through WM_GETTEXT) from an edit box that has
                // the ES_PASSWORD style throws an InvalidOperationException.
                throw new InvalidOperationException();
            }
 
            return Text(_hwnd);
        }
 
        private bool IsInsideOfCombo()
        {
            IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(_hwnd, NativeMethods.GA_PARENT);
 
            if (hwndParent == IntPtr.Zero)
            {
                return false;
            }
 
            // Test for combo
            NativeMethods.COMBOBOXINFO cbInfo = new NativeMethods.COMBOBOXINFO(NativeMethods.comboboxInfoSize);
 
            if (WindowsComboBox.GetComboInfo(hwndParent, ref cbInfo) && cbInfo.hwndItem == _hwnd)
            {
                return true;
            }
 
            return false;
        }
 
        private bool IsInsideOfSpinner()
        {
            IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(_hwnd, NativeMethods.GA_PARENT);
 
            if (hwndParent != IntPtr.Zero)
            {
                // Test for spinner - Create checks if the element is a spinner
                if (WinformsSpinner.Create(hwndParent, 0) != null)
                {
                    return true;
                }
            }
 
            return WindowsSpinner.IsSpinnerEdit(_hwnd);
        }
 
        private static bool IsInsideOfIPAddress(IntPtr hwnd)
        {
            IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(hwnd, NativeMethods.GA_PARENT);
 
            if (hwndParent != IntPtr.Zero)
            {
                string classname = Misc.GetClassName(hwndParent);
                if (classname.Equals("SysIPAddress32"))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        private static bool IsInsideOfListView(IntPtr hwnd)
        {
            IntPtr hwndParent = NativeMethodsSetLastError.GetAncestor(hwnd, NativeMethods.GA_PARENT);
 
            if (hwndParent != IntPtr.Zero)
            {
                string classname = Misc.GetClassName(hwndParent);
                if (classname.Equals("SysListView32"))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        // given the low-order word of a non-negative integer that is at most 0xffff greater than some floor integer
        // we calculate what the original integer was.
        private static int IntFromLowWord(ushort lowWord, int floor)
        {
            // get the high-word from our floor integer
            Debug.Assert(floor >= 0);
            short hiWord = (short)(floor >> 16);
 
            // if our unknown integer has cross a 64k boundary from our floor integer
            // then we need to increment the high-word
            ushort floorLowWord = (ushort)(floor & 0xffff);
            if (lowWord < floorLowWord)
                hiWord++;
 
            // combine the adjusted floor high-word with the known low-word to get our integer
            int i = NativeMethods.Util.MAKELONG((int)lowWord, (int)hiWord);
            Debug.Assert(i >= floor);
 
            return i;
        }
 
        #endregion Private methods
 
        // ------------------------------------------------------
        //
        // Private Fields
        //
        // ------------------------------------------------------
 
        #region Private Fields
 
        private EditboxType _type;
 
        #endregion
    }
}