From ae34ee4b35d66c1a06950ad71f5ed9c24ca64925 Mon Sep 17 00:00:00 2001 From: SergeySmirnov-Akvelon Date: Mon, 8 Feb 2021 16:41:51 +0300 Subject: [PATCH] [Accessibility] The 'Edit' element in ComboBox with DropDownStyle(DropDown, Simple) should support ValuePattern and TextPattern #4409 Added ComboBoxUiaTextProvider class to support TextPattern. Its logic is based on the TextBoxBaseUiaTextProvider, but we use the native control handle, which is responsible for editing Changed the value of the "nativeDebugging" flag to "false", because the text pattern works incorrectly without this in the "WinFormTests" project Added unit tests --- .../Windows/Forms/Automation/UiaTextRange.cs | 27 +- .../Forms/Automation/UiaTextRangeTests.cs | 22 +- .../src/PublicAPI.Unshipped.txt | 2 + .../ComboBox.ComboBoxChildEditUiaProvider.cs | 19 +- .../Forms/ComboBox.ComboBoxUiaTextProvider.cs | 426 ++++++ .../src/System/Windows/Forms/ComboBox.cs | 46 +- .../TextBoxBase.TextBoxBaseUiaTextProvider.cs | 12 +- .../src/System/Windows/Forms/TextBoxBase.cs | 2 +- .../Properties/launchSettings.json | 2 +- ...boBox.ComboBoxChildEditUiaProviderTests.cs | 48 + .../ComboBox.ComboBoxUiaTextProviderTests.cs | 1161 +++++++++++++++++ 11 files changed, 1744 insertions(+), 23 deletions(-) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxUiaTextProvider.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxUiaTextProviderTests.cs diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextRange.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextRange.cs index 478cf0cda5c..58269255171 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextRange.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextRange.cs @@ -273,6 +273,22 @@ void ITextRangeProvider.ExpandToEnclosingUnit(TextUnit unit) double[] ITextRangeProvider.GetBoundingRectangles() { + Rectangle ownerBounds = Drawing.Rectangle.Empty; + + if (_enclosingElement.GetPropertyValue(UIA.BoundingRectanglePropertyId) is Rectangle boundsPropertyValue) + { + ownerBounds = boundsPropertyValue; + } + + // We accumulate rectangles onto a list. + List rectangles = new List(); + + if (_provider.TextLength == 0) + { + rectangles.Add(ownerBounds); + return _provider.RectListToDoubleArray(rectangles); + } + // if this is an end of line if (Start == _provider.TextLength) { @@ -292,12 +308,6 @@ double[] ITextRangeProvider.GetBoundingRectangles() string text = _provider.Text; ValidateEndpoints(); - Rectangle ownerBounds = Drawing.Rectangle.Empty; - - if (_enclosingElement.GetPropertyValue(UIA.BoundingRectanglePropertyId) is object boundsPropertyValue) - { - ownerBounds = (Rectangle)boundsPropertyValue; - } // Get the mapping from client coordinates to screen coordinates. Point mapClientToScreen = new Point(ownerBounds.X, ownerBounds.Y); @@ -305,17 +315,12 @@ double[] ITextRangeProvider.GetBoundingRectangles() // Clip the rectangles to the edit control's formatting rectangle. Rectangle clippingRectangle = _provider.BoundingRectangle; - // We accumulate rectangles onto a list. - List rectangles; - if (_provider.IsMultiline) { rectangles = GetMultilineBoundingRectangles(text, mapClientToScreen, clippingRectangle); return _provider.RectListToDoubleArray(rectangles); } - rectangles = new List(); - // Figure out the rectangle for this one line. Point startPoint = _provider.GetPositionFromChar(Start); Point endPoint = _provider.GetPositionFromCharForUpperRightCorner(End - 1, text); diff --git a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextRangeTests.cs b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextRangeTests.cs index f0d79be6504..e6cfd5ebb12 100644 --- a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextRangeTests.cs +++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextRangeTests.cs @@ -518,10 +518,26 @@ internal void UiaTextRange_ITextRangeProvider_GetAttributeValue_Returns_Correct( Assert.Equal(attributeValue, actual); } + [StaFact] + public void UiaTextRange_ITextRangeProvider_GetBoundingRectangles_ReturnsEmpty_for_EmptyText() + { + Mock enclosingElementMock = new Mock(MockBehavior.Strict); + enclosingElementMock.Setup(m => m.GetPropertyValue(UIA.BoundingRectanglePropertyId)).Returns(new Rectangle(10, 33, 96, 19)); + IRawElementProviderSimple enclosingElement = enclosingElementMock.Object; + Mock providerMock = new Mock(MockBehavior.Strict); + providerMock.Setup(p => p.TextLength).Returns(0); + UiaTextProvider provider = providerMock.Object; + UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 0, end: 0); + var actual = ((ITextRangeProvider)textRange).GetBoundingRectangles(); + Assert.Equal(new double[] { 10, 33, 96, 19 }, actual); + } + [StaFact] public void UiaTextRange_ITextRangeProvider_GetBoundingRectangles_ReturnsEmpty_for_DegenerateRange() { - IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object; + Mock enclosingElementMock = new Mock(MockBehavior.Strict); + enclosingElementMock.Setup(m => m.GetPropertyValue(UIA.BoundingRectanglePropertyId)).Returns(new Rectangle(10, 33, 96, 19)); + IRawElementProviderSimple enclosingElement = enclosingElementMock.Object; Mock providerMock = new Mock(MockBehavior.Strict); providerMock.Setup(p => p.TextLength).Returns(5); UiaTextProvider provider = providerMock.Object; @@ -533,7 +549,9 @@ public void UiaTextRange_ITextRangeProvider_GetBoundingRectangles_ReturnsEmpty_f [StaFact] public void UiaTextRange_ITextRangeProvider_GetBoundingRectangles_ReturnsExpected_for_Endline() { - IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object; + Mock enclosingElementMock = new Mock(MockBehavior.Strict); + enclosingElementMock.Setup(m => m.GetPropertyValue(UIA.BoundingRectanglePropertyId)).Returns(new Rectangle(10, 33, 96, 19)); + IRawElementProviderSimple enclosingElement = enclosingElementMock.Object; Mock providerMock = new Mock(MockBehavior.Strict); providerMock.Setup(p => p.TextLength).Returns(3); providerMock.Setup(p => p.PointToScreen(It.IsAny())).Returns(Point.Empty); diff --git a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt index b5b59d86832..f3c5e1d9e8f 100644 --- a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt @@ -1,3 +1,5 @@ +~override System.Windows.Forms.ComboBox.OnMouseDown(System.Windows.Forms.MouseEventArgs e) -> void +~override System.Windows.Forms.ComboBox.OnKeyUp(System.Windows.Forms.KeyEventArgs e) -> void ~override System.Windows.Forms.ListView.OnLostFocus(System.EventArgs e) -> void ~override System.Windows.Forms.TabControl.OnGotFocus(System.EventArgs e) -> void ~override System.Windows.Forms.TabControl.OnLostFocus(System.EventArgs e) -> void diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProvider.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProvider.cs index 316e38dfa46..7e905970521 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProvider.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProvider.cs @@ -20,6 +20,7 @@ internal class ComboBoxChildEditUiaProvider : ChildAccessibleObject private const string COMBO_BOX_EDIT_AUTOMATION_ID = "1001"; private readonly ComboBox _owner; + private readonly ComboBoxUiaTextProvider _textProvider; private readonly IntPtr _handle; /// @@ -31,6 +32,8 @@ public ComboBoxChildEditUiaProvider(ComboBox owner, IntPtr childEditControlhandl { _owner = owner; _handle = childEditControlhandle; + _textProvider = new ComboBoxUiaTextProvider(owner); + UseTextProviders(_textProvider, _textProvider); } /// @@ -110,7 +113,12 @@ internal override object GetPropertyValue(UiaCore.UIA propertyID) return _handle; case UiaCore.UIA.IsOffscreenPropertyId: return false; - + case UiaCore.UIA.IsTextPatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.TextPatternId); + case UiaCore.UIA.IsTextPattern2AvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.TextPattern2Id); + case UiaCore.UIA.IsValuePatternAvailablePropertyId: + return IsPatternSupported(UiaCore.UIA.ValuePatternId); default: return base.GetPropertyValue(propertyID); } @@ -127,6 +135,15 @@ internal override UiaCore.IRawElementProviderSimple HostRawElementProvider internal override bool IsIAccessibleExSupported() => true; + internal override bool IsPatternSupported(UiaCore.UIA patternId) => + patternId switch + { + UiaCore.UIA.ValuePatternId => true, + UiaCore.UIA.TextPatternId => true, + UiaCore.UIA.TextPattern2Id => true, + _ => base.IsPatternSupported(patternId) + }; + /// /// Gets the runtime ID. /// diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxUiaTextProvider.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxUiaTextProvider.cs new file mode 100644 index 00000000000..c936e590fe7 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.ComboBoxUiaTextProvider.cs @@ -0,0 +1,426 @@ +// 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. + +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms.Automation; +using static Interop; +using static Interop.User32; + +namespace System.Windows.Forms +{ + public partial class ComboBox + { + internal class ComboBoxUiaTextProvider : UiaTextProvider2 + { + /// + /// Since the TextBox inside the ComboBox is always single-line, for optimization + /// we always return 0 as the index of lines + /// + private const int OwnerChildEditLineIndex = 0; + + /// + /// Since the TextBox inside the ComboBox is always single-line, for optimization + /// we always return 1 as the number of lines + /// + private const int OwnerChildEditLinesCount = 1; + + private readonly IHandle _owningChildEdit; + + private readonly ComboBox _owningComboBox; + + public ComboBoxUiaTextProvider(ComboBox owner) + { + _owningComboBox = owner ?? throw new ArgumentNullException(nameof(owner)); + Debug.Assert(_owningComboBox.IsHandleCreated); + + _owningChildEdit = owner._childEdit; + } + + public override Rectangle BoundingRectangle + => _owningComboBox.IsHandleCreated + ? GetFormattingRectangle() + : Rectangle.Empty; + + public override UiaCore.ITextRangeProvider DocumentRange => new UiaTextRange(new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject), this, 0, TextLength); + + public override ES EditStyle + => _owningComboBox.IsHandleCreated + ? GetEditStyle(_owningChildEdit) + : ES.LEFT; + + public override int FirstVisibleLine + => _owningComboBox.IsHandleCreated + ? 0 + : -1; + + public override bool IsMultiline => false; + + public override bool IsReadingRTL + => _owningComboBox.IsHandleCreated + ? WindowExStyle.HasFlag(WS_EX.RTLREADING) + : false; + + public override bool IsReadOnly => false; + + public override bool IsScrollable + { + get + { + if (!_owningComboBox.IsHandleCreated) + { + return false; + } + + ES extendedStyle = (ES)(long)GetWindowLong(_owningChildEdit, GWL.STYLE); + return extendedStyle.HasFlag(ES.AUTOHSCROLL); + } + } + + public override int LinesCount + => _owningComboBox.IsHandleCreated + ? OwnerChildEditLinesCount + : -1; + + public override int LinesPerPage + { + get + { + if (!_owningComboBox.IsHandleCreated) + { + return -1; + } + + if (_owningComboBox.ChildEditAccessibleObject.BoundingRectangle.IsEmpty) + { + return 0; + } + + return OwnerChildEditLinesCount; + } + } + + public override LOGFONTW Logfont + => _owningComboBox.IsHandleCreated + ? LOGFONTW.FromFont(_owningComboBox.Font) + : default; + + public override UiaCore.SupportedTextSelection SupportedTextSelection => UiaCore.SupportedTextSelection.Single; + + public override string Text + => _owningComboBox.IsHandleCreated + ? User32.GetWindowText(new HandleRef(_owningComboBox, _owningChildEdit.Handle)) + : string.Empty; + + public override int TextLength + => _owningComboBox.IsHandleCreated + ? (int)(long)SendMessageW(_owningChildEdit, WM.GETTEXTLENGTH) + : -1; + + public override WS_EX WindowExStyle + => _owningComboBox.IsHandleCreated + ? GetWindowExStyle(_owningChildEdit) + : WS_EX.LEFT; + + public override WS WindowStyle + => _owningComboBox.IsHandleCreated + ? GetWindowStyle(_owningChildEdit) + : WS.OVERLAPPED; + + public override UiaCore.ITextRangeProvider? GetCaretRange(out BOOL isActive) + { + isActive = BOOL.FALSE; + + if (!_owningComboBox.IsHandleCreated) + { + return null; + } + + object? hasKeyboardFocus = _owningComboBox.ChildEditAccessibleObject.GetPropertyValue(UiaCore.UIA.HasKeyboardFocusPropertyId); + if (hasKeyboardFocus is true) + { + isActive = BOOL.TRUE; + } + + var internalAccessibleObject = new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject); + + return new UiaTextRange(internalAccessibleObject, this, _owningComboBox.SelectionStart, _owningComboBox.SelectionStart); + } + + public override int GetLineFromCharIndex(int charIndex) + => _owningComboBox.IsHandleCreated + ? OwnerChildEditLineIndex + : -1; + + public override int GetLineIndex(int line) + => _owningComboBox.IsHandleCreated + ? OwnerChildEditLineIndex + : -1; + + public override Point GetPositionFromChar(int charIndex) + => _owningComboBox.IsHandleCreated + ? GetPositionFromCharIndex(charIndex) + : Point.Empty; + + // A variation on EM_POSFROMCHAR that returns the upper-right corner instead of upper-left. + public override Point GetPositionFromCharForUpperRightCorner(int startCharIndex, string text) + { + if (!_owningComboBox.IsHandleCreated || startCharIndex < 0 || startCharIndex >= text.Length) + { + return Point.Empty; + } + + char ch = text[startCharIndex]; + Point pt; + + if (char.IsControl(ch)) + { + if (ch == '\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 = startCharIndex < TextLength - 1 && GetLineFromCharIndex(startCharIndex + 1) == GetLineFromCharIndex(startCharIndex); + return GetPositionFromCharIndex(useNext ? startCharIndex + 1 : startCharIndex); + } + + pt = GetPositionFromCharIndex(startCharIndex); + + if (ch == '\r' || ch == '\n') + { + pt.X += EndOfLineWidth; // add 2 px to show the end of line + } + + // return the UL corner of the rest characters because these characters have no width + return pt; + } + + // get the UL corner of the character + pt = GetPositionFromCharIndex(startCharIndex); + + // add the width of the character at that position. + if (GetTextExtentPoint32(ch, out Size size)) + { + pt.X += size.Width; + } + + return pt; + } + + public override UiaCore.ITextRangeProvider[]? GetSelection() + { + if (!_owningComboBox.IsHandleCreated) + { + return null; + } + + // First caret position of a selected text + int start = 0; + // Last caret position of a selected text + int end = 0; + + // Returns info about the selected text range. + // If there is no selection, start and end parameters are the position of the caret. + SendMessageW(_owningChildEdit, (WM)EM.GETSEL, ref start, ref end); + + var internalAccessibleObject = new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject); + + return new UiaCore.ITextRangeProvider[] { new UiaTextRange(internalAccessibleObject, this, start, end) }; + } + + public override void GetVisibleRangePoints(out int visibleStart, out int visibleEnd) + { + visibleStart = 0; + visibleEnd = 0; + + if (!_owningComboBox.IsHandleCreated || IsDegenerate(_owningComboBox.ClientRectangle)) + { + return; + } + + Rectangle rectangle = GetFormattingRectangle(); + if (IsDegenerate(rectangle)) + { + return; + } + + // Formatting rectangle is the boundary, which we need to inflate by 1 + // in order to read characters within the rectangle + Point ptStart = new Point(rectangle.X + 1, rectangle.Y + 1); + Point ptEnd = new Point(rectangle.Right - 1, rectangle.Bottom - 1); + + visibleStart = GetCharIndexFromPosition(ptStart); + visibleEnd = GetCharIndexFromPosition(ptEnd) + 1; // Add 1 to get a caret position after received character + + return; + + bool IsDegenerate(Rectangle rect) + => rect.IsEmpty || rect.Width <= 0 || rect.Height <= 0; + } + + public override UiaCore.ITextRangeProvider[]? GetVisibleRanges() + { + if (!_owningComboBox.IsHandleCreated) + { + return null; + } + + GetVisibleRangePoints(out int start, out int end); + var internalAccessibleObject = new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject); + + return new UiaCore.ITextRangeProvider[] { new UiaTextRange(internalAccessibleObject, this, start, end) }; + } + + public override bool LineScroll(int charactersHorizontal, int linesVertical) + // If the EM_LINESCROLL message is sent to a single-line edit control, the return value is FALSE. + => false; + + public override Point PointToScreen(Point pt) + { + User32.MapWindowPoints(_owningChildEdit.Handle, IntPtr.Zero, ref pt, 1); + return pt; + } + + /// + /// Exposes a text range that contains the text that is the target of the annotation associated with the specified annotation element. + /// + /// + /// The provider for an element that implements the IAnnotationProvider interface. + /// The annotation element is a sibling of the element that implements the interface for the document. + /// + /// + /// A text range that contains the annotation target text. + /// + public override UiaCore.ITextRangeProvider RangeFromAnnotation(UiaCore.IRawElementProviderSimple annotationElement) + { + InternalAccessibleObject internalAccessibleObject = new(_owningComboBox.ChildEditAccessibleObject); + return new UiaTextRange(internalAccessibleObject, this, 0, 0); + } + + public override UiaCore.ITextRangeProvider? RangeFromChild(UiaCore.IRawElementProviderSimple childElement) + { + // We don't have any children so this call returns null. + Debug.Fail("Text edit control cannot have a child element."); + return null; + } + + /// + /// Returns the degenerate (empty) text range nearest to the specified screen coordinates. + /// + /// The location in screen coordinates. + /// A degenerate range nearest the specified location. Null is never returned. + public override UiaCore.ITextRangeProvider? RangeFromPoint(Point screenLocation) + { + if (!_owningComboBox.IsHandleCreated) + { + return null; + } + + Point clientLocation = screenLocation; + + // Convert screen to client coordinates. + // (Essentially ScreenToClient but MapWindowPoints accounts for window mirroring using WS_EX_LAYOUTRTL.) + if (MapWindowPoints(default, _owningChildEdit, ref clientLocation, 1) == 0) + { + return new UiaTextRange(new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject), this, 0, 0); + } + + // 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. + RECT clientRectangle = _owningComboBox.ChildEditAccessibleObject.BoundingRectangle; + + clientLocation.X = Math.Max(clientLocation.X, clientRectangle.left); + clientLocation.X = Math.Min(clientLocation.X, clientRectangle.right); + clientLocation.Y = Math.Max(clientLocation.Y, clientRectangle.top); + clientLocation.Y = Math.Min(clientLocation.Y, clientRectangle.bottom); + + // Get the character at those client coordinates. + int start = GetCharIndexFromPosition(clientLocation); + + return new UiaTextRange(new InternalAccessibleObject(_owningComboBox.ChildEditAccessibleObject), this, start, start); + } + public override void SetSelection(int start, int end) + { + if (!_owningComboBox.IsHandleCreated) + { + return; + } + + if (start < 0 || start > TextLength) + { + Debug.Fail("SetSelection start is out of text range."); + return; + } + + if (end < 0 || end > TextLength) + { + Debug.Fail("SetSelection end is out of text range."); + return; + } + + SendMessageW(_owningChildEdit, (WM)EM.SETSEL, (IntPtr)start, (IntPtr)end); + } + + private int GetCharIndexFromPosition(Point pt) + { + int index = (int)(long)User32.SendMessageW(_owningChildEdit, (WM)EM.CHARFROMPOS, IntPtr.Zero, PARAM.FromLowHigh(pt.X, pt.Y)); + index = PARAM.LOWORD(index); + + if (index < 0) + { + index = 0; + } + else + { + string t = Text; + // EM_CHARFROMPOS will return an invalid number if the last character in the RichEdit + // is a newline. + // + if (index >= t.Length) + { + index = Math.Max(t.Length - 1, 0); + } + } + + return index; + } + + private RECT GetFormattingRectangle() + { + // Send an EM_GETRECT message to find out the bounding rectangle. + RECT rectangle = new RECT(); + SendMessageW(_owningChildEdit, (WM)EM.GETRECT, IntPtr.Zero, ref rectangle); + + return rectangle; + } + + private Point GetPositionFromCharIndex(int index) + { + if (index < 0 || index >= Text.Length) + { + return Point.Empty; + } + + int i = (int)(long)SendMessageW(_owningChildEdit, (WM)EM.POSFROMCHAR, (IntPtr)index); + + return new Point(PARAM.SignedLOWORD(i), PARAM.SignedHIWORD(i)); + } + + private bool GetTextExtentPoint32(char item, out Size size) + { + size = new Size(); + + using var hdc = new GetDcScope(_owningChildEdit.Handle); + if (hdc.IsNull) + { + return false; + } + + // Add the width of the character at that position. + return Gdi32.GetTextExtentPoint32W(hdc, item.ToString(), 1, ref size).IsTrue(); + } + } + } +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.cs index 6c33fbb228f..b4aab5a8683 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.cs @@ -1927,6 +1927,16 @@ private void OnMouseEnterInternal(EventArgs args) } } + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (_childEdit is not null && ChildEditAccessibleObject.Bounds.Contains(PointToScreen(e.Location))) + { + ChildEditAccessibleObject.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); + } + } + /// /// Helper to handle MouseLeave. /// @@ -2607,6 +2617,30 @@ protected override void OnKeyPress(KeyPressEventArgs e) } } + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + + if (_childEdit is not null && ContainsNavigationKeyCode(e.KeyCode)) + { + ChildEditAccessibleObject.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); + } + } + + private bool ContainsNavigationKeyCode(Keys keyCode) + { + switch (keyCode) + { + case Keys.Home: + case Keys.End: + case Keys.Left: + case Keys.Right: + return true; + default: + return false; + } + } + /// /// This is the code that actually fires the measuereItem event. Don't /// forget to call base.onMeasureItem() to ensure that measureItem @@ -2710,6 +2744,11 @@ protected override void OnSelectedIndexChanged(EventArgs e) accessibleObject.SetComboBoxItemSelection(); } + if (_childEdit is not null) + { + ChildEditAccessibleObject.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); + } + // set the position in the dataSource, if there is any // we will only set the position in the currencyManager if it is different // from the SelectedIndex. Setting CurrencyManager::Position (even w/o changing it) @@ -2869,6 +2908,11 @@ protected override void OnTextChanged(EventArgs e) // Call the base base.OnTextChanged(e); } + + if (_childEdit is not null) + { + ChildEditAccessibleObject.RaiseAutomationEvent(UiaCore.UIA.Text_TextChangedEventId); + } } [EditorBrowsable(EditorBrowsableState.Advanced)] @@ -2911,7 +2955,7 @@ private void UpdateControl(bool recreate) protected override void OnResize(EventArgs e) { base.OnResize(e); - if (DropDownStyle == ComboBoxStyle.Simple) + if (DropDownStyle == ComboBoxStyle.Simple && IsHandleCreated) { // simple style combo boxes have more painting problems than you can shake a stick at InvalidateEverything(); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs index f93b7452258..7555975ed3f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs @@ -34,7 +34,7 @@ public TextBoxBaseUiaTextProvider(TextBoxBase owner) // Last caret position of a selected text int end = 0; - // Returns info about a selected text range. + // Returns info about the selected text range. // If there is no selection, start and end parameters are the position of the caret. SendMessageW(_owningTextBoxBase, (WM)EM.GETSEL, ref start, ref end); @@ -63,7 +63,7 @@ public TextBoxBaseUiaTextProvider(TextBoxBase owner) } /// - /// Returns the degenerate (empty) text range nearest to the specified screen coordinates. + /// Returns the degenerate (empty) text range nearest to the specified screen coordinates. /// /// The location in screen coordinates. /// A degenerate range nearest the specified location. Null is never returned. @@ -126,14 +126,14 @@ public TextBoxBaseUiaTextProvider(TextBoxBase owner) public override Point PointToScreen(Point pt) => _owningTextBoxBase.PointToScreen(pt); /// - /// Exposes a text range that contains the text that is the target of the annotation associated with the specified annotation element. + /// Exposes a text range that contains the text that is the target of the annotation associated with the specified annotation element. /// /// - /// The provider for an element that implements the IAnnotationProvider interface. - /// The annotation element is a sibling of the element that implements the interface for the document. + /// The provider for an element that implements the IAnnotationProvider interface. + /// The annotation element is a sibling of the element that implements the interface for the document. /// /// - /// A text range that contains the annotation target text. + /// A text range that contains the annotation target text. /// public override UiaCore.ITextRangeProvider RangeFromAnnotation(UiaCore.IRawElementProviderSimple annotationElement) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs index daf3d22f381..9679b72cc7c 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs @@ -1630,7 +1630,7 @@ public virtual char GetCharFromPosition(Point pt) /// public virtual int GetCharIndexFromPosition(Point pt) { - int index = (int)User32.SendMessageW(this, (WM)EM.CHARFROMPOS, IntPtr.Zero, PARAM.FromLowHigh(pt.X, pt.Y)); + int index = (int)(long)User32.SendMessageW(this, (WM)EM.CHARFROMPOS, IntPtr.Zero, PARAM.FromLowHigh(pt.X, pt.Y)); index = PARAM.LOWORD(index); if (index < 0) diff --git a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Properties/launchSettings.json b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Properties/launchSettings.json index 5f3c5d0ca08..08e953811bc 100644 --- a/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Properties/launchSettings.json +++ b/src/System.Windows.Forms/tests/IntegrationTests/WinformsControlsTest/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "WinformsControlsTest": { "commandName": "Project", - "nativeDebugging": true + "nativeDebugging": false } } } \ No newline at end of file diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProviderTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProviderTests.cs index 81071c188fe..b6cbba245dc 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProviderTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxChildEditUiaProviderTests.cs @@ -106,5 +106,53 @@ public void ComboBoxChildEditUiaProvider_FragmentNavigate_NextSibling_ReturnsExp Assert.Equal(expectedItem, nextItem); Assert.True(comboBox.IsHandleCreated); } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxChildEditUiaProvider_SupportsTextPattern(ComboBoxStyle comboBoxStyle) + { + using ComboBox comboBox = new ComboBox + { + DropDownStyle = comboBoxStyle + }; + + comboBox.CreateControl(); + AccessibleObject accessibleObject = comboBox.ChildEditAccessibleObject; + + Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsTextPatternAvailablePropertyId)); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxChildEditUiaProvider_SupportsTextPattern2(ComboBoxStyle comboBoxStyle) + { + using ComboBox comboBox = new ComboBox + { + DropDownStyle = comboBoxStyle + }; + + comboBox.CreateControl(); + AccessibleObject accessibleObject = comboBox.ChildEditAccessibleObject; + + Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsTextPattern2AvailablePropertyId)); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxChildEditUiaProvider_SupportsValuetPattern(ComboBoxStyle comboBoxStyle) + { + using ComboBox comboBox = new ComboBox + { + DropDownStyle = comboBoxStyle + }; + + comboBox.CreateControl(); + AccessibleObject accessibleObject = comboBox.ChildEditAccessibleObject; + + Assert.True((bool)accessibleObject.GetPropertyValue(UiaCore.UIA.IsValuePatternAvailablePropertyId)); + } } } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxUiaTextProviderTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxUiaTextProviderTests.cs new file mode 100644 index 00000000000..bda9aa5a726 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ComboBox.ComboBoxUiaTextProviderTests.cs @@ -0,0 +1,1161 @@ +// 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. + +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms.Automation; +using Xunit; +using static Interop; +using static Interop.User32; + +namespace System.Windows.Forms.Tests +{ + public class ComboBox_ComboBoxUiaTextProviderTests : IClassFixture + { + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_Ctor_DoesntCreateControlHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsFact] + public void ComboBoxUiaTextProvider_Ctor_ThrowsException_IfOwnerIsNull() + { + Assert.Throws(() => new ComboBox.ComboBoxUiaTextProvider(null)); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_IsMultiline_IsFalse(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.IsMultiline); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_IsReadOnly_IsFalse(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.IsReadOnly); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_IsScrollable_IsTrue(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.True(provider.IsScrollable); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_IsScrollable_False_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.IsScrollable); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetWindowStyle_ReturnsNoneForNotInitializedControl(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(WS.OVERLAPPED, provider.WindowStyle); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, RightToLeft.Yes, true)] + [InlineData(ComboBoxStyle.DropDown, RightToLeft.No, false)] + [InlineData(ComboBoxStyle.Simple, RightToLeft.Yes, true)] + [InlineData(ComboBoxStyle.Simple, RightToLeft.No, false)] + public void ComboBoxUiaTextProvider_IsReadingRTL_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, RightToLeft rightToLeft, bool expectedResult) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, RightToLeft = rightToLeft }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(expectedResult, provider.IsReadingRTL); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, RightToLeft.Yes)] + [InlineData(ComboBoxStyle.DropDown, RightToLeft.No)] + [InlineData(ComboBoxStyle.Simple, RightToLeft.Yes)] + [InlineData(ComboBoxStyle.Simple, RightToLeft.No)] + public void ComboBoxUiaTextProvider_IsReadingRTL_ReturnsFalse_WithoutHandle(ComboBoxStyle dropDownStyle, RightToLeft rightToLeft) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, RightToLeft = rightToLeft }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.IsReadingRTL); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_DocumentRange_IsNotNull_WorksCorrectly(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + Assert.Equal(comboBox.ChildEditAccessibleObject, provider.DocumentRange.GetEnclosingElement().TestAccessor().Dynamic.publicIAccessible); + Assert.Equal(provider, provider.DocumentRange.TestAccessor().Dynamic._provider); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_DocumentRange_IsNull_ThowException(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + Assert.Throws(() => provider.DocumentRange); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_SupportedTextSelection_IsNotNull(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + UiaCore.SupportedTextSelection uiaTextRange = provider.SupportedTextSelection; + + Assert.Equal(UiaCore.SupportedTextSelection.Single, uiaTextRange); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetCaretRange_IsNotNull(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + UiaCore.ITextRangeProvider uiaTextRange = provider.GetCaretRange(out _); + + Assert.NotNull(uiaTextRange); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetCaretRange_IsNull_IfHandleIsNotCreated(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + UiaCore.ITextRangeProvider uiaTextRange = provider.GetCaretRange(out _); + + Assert.Null(uiaTextRange); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_LinesPerPage_ReturnsMinusOne_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(-1, provider.LinesPerPage); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_LinesPerPage_ReturnsOne_WithHandle(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(1, provider.LinesPerPage); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 0)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 50)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 100)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, -5)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 0)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 50)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 100)] + [InlineData(ComboBoxStyle.Simple, 50, 20, -5)] + public void ComboBoxUiaTextProvider_GetLineFromCharIndex_ReturnsZero( + ComboBoxStyle dropDownStyle, + int width, + int height, + int charIndex) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(width, height) }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing GetLineFromCharIndex method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + int actualLine = provider.GetLineFromCharIndex(charIndex); + + Assert.Equal(0, actualLine); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 5)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 5)] + public void ComboBoxUiaTextProvider_GetLineFromCharIndex_ReturnsMinusOne_WithoutHandle( + ComboBoxStyle dropDownStyle, + int width, + int height, + int charIndex) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(width, height) }; + comboBox.Items.Add("Some test text for testing GetLineFromCharIndex method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + int actualLine = provider.GetLineFromCharIndex(charIndex); + + Assert.Equal(-1, actualLine); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_GetLineIndex_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, 50, 20, 0 }; + yield return new object[] { comboBoxStyle, 50, 20, 3 }; + yield return new object[] { comboBoxStyle, 50, 50, 3 }; + yield return new object[] { comboBoxStyle, 100, 50, 3 }; + yield return new object[] { comboBoxStyle, 50, 50, 100 }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetLineIndex_TestData))] + public void ComboBoxUiaTextProvider_GetLineIndex_ReturnsCorrectValue( + ComboBoxStyle dropDownStyle, + int width, + int height, + int lineIndex) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(width, height) }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing GetLineIndex method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + int actualIndex = provider.GetLineIndex(lineIndex); + + Assert.Equal(0, actualIndex); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetLineIndex_TestData))] + public void ComboBoxUiaTextProvider_GetLineIndex_ReturnsMinusOne_WithoutHandle( + ComboBoxStyle dropDownStyle, + int width, + int height, + int lineIndex) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(width, height) }; + comboBox.Items.Add("Some test text for testing GetLineIndex method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + int actualIndex = provider.GetLineIndex(lineIndex); + + Assert.Equal(-1, actualIndex); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetLogfont_ReturnsCorrectValue(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + LOGFONTW expected = LOGFONTW.FromFont(comboBox.Font); + LOGFONTW actual = provider.Logfont; + Assert.False(string.IsNullOrEmpty(actual.FaceName.ToString())); + Assert.Equal(expected, actual); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetLogfont_ReturnsEmpty_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + LOGFONTW expected = new LOGFONTW(); + + LOGFONTW actual = provider.Logfont; + + Assert.True(string.IsNullOrEmpty(actual.FaceName.ToString())); + Assert.Equal(expected, actual); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_GetPositionFromChar_TestData() + { + yield return new object[] { ComboBoxStyle.DropDown, new Size(50, 20), "Some test text for testing", 0, new Point(1, 0) }; + yield return new object[] { ComboBoxStyle.DropDown, new Size(50, 20), "Some test text for testing", 15, new Point(79, 0) }; + + yield return new object[] { ComboBoxStyle.Simple, new Size(50, 20), "Some test text for testing", 0, new Point(1, 0) }; + yield return new object[] { ComboBoxStyle.Simple, new Size(50, 20), "Some test text for testing", 15, new Point(79, 0) }; + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetPositionFromChar_TestData))] + public void ComboBoxUiaTextProvider_GetPositionFromChar_ReturnsCorrectValue(ComboBoxStyle dropdownStyle, Size size, string text, int charIndex, Point expectedPoint) + { + using ComboBox comboBox = new ComboBox() { Size = size, DropDownStyle = dropdownStyle }; + comboBox.CreateControl(); + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Point actualPoint = provider.GetPositionFromChar(charIndex); + + Assert.True(actualPoint.X >= expectedPoint.X - 1 || actualPoint.X <= expectedPoint.X + 1); + Assert.True(actualPoint.Y >= expectedPoint.Y - 1 || actualPoint.Y <= expectedPoint.Y + 1); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + public static IEnumerable ComboBoxUiaTextProvider_GetPositionFromChar_WithoutHanlde_TestData() + { + yield return new object[] { ComboBoxStyle.DropDown, new Size(50, 20), "Some test text for testing", 0 }; + yield return new object[] { ComboBoxStyle.DropDown, new Size(50, 20), "Some test text for testing", 15 }; + + yield return new object[] { ComboBoxStyle.Simple, new Size(50, 20), "Some test text for testing", 0 }; + yield return new object[] { ComboBoxStyle.Simple, new Size(50, 20), "Some test text for testing", 15 }; + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetPositionFromChar_WithoutHanlde_TestData))] + public void ComboBoxUiaTextProvider_GetPositionFromChar_ReturnsEmpty_WithoutHanlde(ComboBoxStyle dropDownStyle, Size size, string text, int charIndex) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Point actualPoint = provider.GetPositionFromChar(charIndex); + + Assert.Equal(Point.Empty, actualPoint); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, new Size(50, 20), "", 0, new Point(0, 0) }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", 100, new Point(0, 0) }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", -1, new Point(0, 0) }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", 12, new Point(71, 0) }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \n text", 10, new Point(56, 0) }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \r\n text", 10, new Point(56, 0) }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \r\n text", 12, new Point(60, 0) }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \t text", 10, new Point(57, 0) }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_TestData))] + public void ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, Size size, string text, int charIndex, Point expectedPoint) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.CreateControl(); + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Point actualPoint = provider.GetPositionFromCharForUpperRightCorner(charIndex, comboBox.Text); + + Assert.True(actualPoint.X >= expectedPoint.X - 1 || actualPoint.X <= expectedPoint.X + 1); + Assert.True(actualPoint.Y >= expectedPoint.Y - 1 || actualPoint.Y <= expectedPoint.Y + 1); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + public static IEnumerable ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_WithoutHandle_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, new Size(50, 20), "", 0 }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", 100 }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", -1 }; + yield return new object[] { comboBoxStyle, new Size(50, 20), "Some test text", 12 }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \n text", 10 }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \r\n text", 10 }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \r\n text", 12 }; + yield return new object[] { comboBoxStyle, new Size(100, 60), "Some test \t text", 10 }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_WithoutHandle_TestData))] + public void ComboBoxUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsMinusOne_WithoutHandle(ComboBoxStyle dropDownStyle, Size size, string text, int charIndex) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Point actualPoint = provider.GetPositionFromCharForUpperRightCorner(charIndex, comboBox.Text); + + Assert.Equal(Point.Empty, actualPoint); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_GetFormattingRectangle_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, new Size(0, 0), new Rectangle(0, 0, 80, 16) }; + int width = comboBoxStyle == ComboBoxStyle.DropDown ? 27 : 44; + yield return new object[] { comboBoxStyle, new Size(50, 50), new Rectangle(0, 0, width, 15) }; + width = comboBoxStyle == ComboBoxStyle.DropDown ? 227 : 244; + yield return new object[] { comboBoxStyle, new Size(250, 100), new Rectangle(0, 0, width, 15) }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetFormattingRectangle_TestData))] + public void ComboBoxUiaTextProvider_GetFormattingRectangle_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, Size size, Rectangle expectedRectangle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Rectangle providerRectangle = provider.BoundingRectangle; + + Assert.Equal(expectedRectangle, providerRectangle); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetFormattingRectangle_ReturnsEmpty_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(250, 100) }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Rectangle providerRectangle = provider.BoundingRectangle; + + Assert.Equal(Drawing.Rectangle.Empty, providerRectangle); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_Text_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, "" }; + yield return new object[] { comboBoxStyle, "Text" }; + yield return new object[] { comboBoxStyle, "Some test text" }; + yield return new object[] { comboBoxStyle, "Some very very very long test text for testing GetTextLength method" }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_Text_TestData))] + public void ComboBoxUiaTextProvider_Text_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, string text) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + string expected = comboBox.Text; + + string actual = provider.Text.Trim('\0'); + + Assert.Equal(expected, actual); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_Text_TestData))] + public void ComboBoxUiaTextProvider_Text_ReturnsEmpty_WithoutHandle(ComboBoxStyle dropDownStyle, string text) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + string actual = provider.Text.Trim('\0'); + + Assert.Equal(string.Empty, actual); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_Text_TestData))] + public void ComboBoxUiaTextProvider_TextLength_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, string text) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(comboBox.Text.Length, provider.TextLength); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_Text_TestData))] + public void ComboBoxUiaTextProvider_TextLength_ReturnsMinusOne_WithoutHandle(ComboBoxStyle dropDownStyle, string text) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.Items.Add(text); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Equal(-1, provider.TextLength); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_WindowExStyle_ReturnsCorrectValue(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + WS_EX actual = provider.WindowExStyle; + Assert.Equal(WS_EX.DEFAULT, actual); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_WindowExStyle_ReturnsLeft_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + WS_EX actual = provider.WindowExStyle; + + Assert.Equal(WS_EX.LEFT, actual); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_EditStyle_ReturnsCorrectValue(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + ES actual = provider.EditStyle; + + Assert.True(actual.HasFlag(ES.LEFT)); + Assert.True(actual.HasFlag(ES.NOHIDESEL)); + Assert.True(actual.HasFlag(ES.AUTOHSCROLL)); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_EditStyle_ReturnsLeft_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + ES actual = provider.EditStyle; + + Assert.Equal(ES.LEFT, actual); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBox_GetVisibleRangePoints_ForSinglelineComboBox_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, new Size(0, 0), 0, 0 }; + yield return new object[] { comboBoxStyle, new Size(0, 20), 0, 0 }; + int expectedEnd = comboBoxStyle == ComboBoxStyle.DropDown ? 2 : 4; + yield return new object[] { comboBoxStyle, new Size(30, 30), 0, expectedEnd }; + expectedEnd = comboBoxStyle == ComboBoxStyle.DropDown ? 4 : 8; + yield return new object[] { comboBoxStyle, new Size(50, 20), 0, expectedEnd }; + yield return new object[] { comboBoxStyle, new Size(150, 20), 0, 26 }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBox_GetVisibleRangePoints_ForSinglelineComboBox_TestData))] + public void ComboBoxUiaTextProvider_GetVisibleRangePoints_ForSinglelineComboBox_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, Size size, int expectedStart, int expectedEnd) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + provider.GetVisibleRangePoints(out int providerVisibleStart, out int providerVisibleEnd); + + Assert.True(providerVisibleStart >= 0); + Assert.True(providerVisibleStart < comboBox.Text.Length); + Assert.True(providerVisibleEnd >= 0); + Assert.True(providerVisibleEnd <= comboBox.Text.Length); + + Assert.Equal(expectedStart, providerVisibleStart); + Assert.Equal(expectedEnd, providerVisibleEnd); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + public static IEnumerable ComboBox_GetVisibleRangePoints_ForSinglelineComboBox_WithoutHandle_TestData() + { + foreach (ComboBoxStyle comboBoxStyle in Enum.GetValues(typeof(ComboBoxStyle))) + { + if (comboBoxStyle == ComboBoxStyle.DropDownList) + { + continue; + } + + yield return new object[] { comboBoxStyle, new Size(0, 0) }; + yield return new object[] { comboBoxStyle, new Size(0, 20) }; + yield return new object[] { comboBoxStyle, new Size(30, 30) }; + yield return new object[] { comboBoxStyle, new Size(50, 20) }; + yield return new object[] { comboBoxStyle, new Size(150, 20) }; + } + } + + [WinFormsTheory] + [MemberData(nameof(ComboBox_GetVisibleRangePoints_ForSinglelineComboBox_WithoutHandle_TestData))] + public void ComboBoxUiaTextProvider_GetVisibleRangePoints_ReturnsZeros_WithoutHandle(ComboBoxStyle dropDownStyle, Size size) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + provider.GetVisibleRangePoints(out int providerVisibleStart, out int providerVisibleEnd); + + Assert.Equal(0, providerVisibleStart); + Assert.Equal(0, providerVisibleEnd); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_GetVisibleRanges_TestData() + { + yield return new object[] { ComboBoxStyle.DropDown, new Size(0, 0) }; + yield return new object[] { ComboBoxStyle.DropDown, new Size(100, 20) }; + yield return new object[] { ComboBoxStyle.Simple, new Size(0, 0) }; + yield return new object[] { ComboBoxStyle.Simple, new Size(100, 20) }; + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetVisibleRanges_TestData))] + public void ComboBoxUiaTextProvider_GetVisibleRanges_ReturnsCorrectValue(ComboBoxStyle dropDownStyle, Size size) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.NotNull(provider.GetVisibleRanges()); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_GetVisibleRanges_TestData))] + public void ComboBoxUiaTextProvider_GetVisibleRanges_ReturnsNull_WithoutHandle(ComboBoxStyle dropDownStyle, Size size) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = size }; + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.Null(provider.GetVisibleRanges()); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_RangeFromChild_DoesntThrowAnException(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + // RangeFromChild doesn't throw an exception + UiaCore.ITextRangeProvider range = provider.RangeFromChild(comboBox.AccessibilityObject); + // RangeFromChild implementation can be changed so this test can be changed too + Assert.Null(range); + } + } + + public static IEnumerable ComboBoxUiaTextProvider_RangeFromPoint_TestData() + { + yield return new object[] { ComboBoxStyle.DropDown, Point.Empty }; + yield return new object[] { ComboBoxStyle.DropDown, new Point(10, 10) }; + yield return new object[] { ComboBoxStyle.Simple, Point.Empty }; + yield return new object[] { ComboBoxStyle.Simple, new Point(10, 10) }; + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_RangeFromPoint_TestData))] + public void ComboBoxUiaTextProvider_RangeFromPoint_DoesntThrowAnException(ComboBoxStyle dropDownStyle, Point point) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle } ; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + UiaTextRange textRangeProvider = provider.RangeFromPoint(point) as UiaTextRange; + + Assert.NotNull(textRangeProvider); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [MemberData(nameof(ComboBoxUiaTextProvider_RangeFromPoint_TestData))] + public void ComboBoxUiaTextProvider_RangeFromPoint_ReturnsNull_WithoutHandle(ComboBoxStyle dropDownStyle, Point point) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + UiaTextRange textRangeProvider = provider.RangeFromPoint(point) as UiaTextRange; + + Assert.Null(textRangeProvider); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 2, 5)] + [InlineData(ComboBoxStyle.DropDown, 0, 10)] + [InlineData(ComboBoxStyle.Simple, 2, 5)] + [InlineData(ComboBoxStyle.Simple, 0, 10)] + public void ComboBoxUiaTextProvider_SetSelection_GetSelection_ReturnCorrectValue(ComboBoxStyle dropDownStyle, int start, int end) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + provider.SetSelection(start, end); + UiaCore.ITextRangeProvider[] selection = provider.GetSelection(); + UiaTextRange textRange = selection[0] as UiaTextRange; + + Assert.NotNull(selection); + Assert.NotNull(textRange); + Assert.Equal(start, textRange.Start); + Assert.Equal(end, textRange.End); + Assert.Equal(start, comboBox.SelectionStart); + Assert.Equal(end, comboBox.SelectionStart + comboBox.SelectionLength); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 2, 5)] + [InlineData(ComboBoxStyle.DropDown, 0, 10)] + [InlineData(ComboBoxStyle.Simple, 2, 5)] + [InlineData(ComboBoxStyle.Simple, 0, 10)] + public void ComboBoxUiaTextProvider_SetSelection_GetSelection_DontWork_WithoutHandle(ComboBoxStyle dropDownStyle, int start, int end) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + provider.SetSelection(start, end); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + + UiaCore.ITextRangeProvider[] selection = provider.GetSelection(); + + Assert.Null(selection); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + Assert.Equal(0, comboBox.SelectionStart); + Assert.Equal(0, comboBox.SelectionStart + comboBox.SelectionLength); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, -5, 10)] + [InlineData(ComboBoxStyle.DropDown, 5, 100)] + [InlineData(ComboBoxStyle.Simple, -5, 10)] + [InlineData(ComboBoxStyle.Simple, 5, 100)] + public void ComboBoxUiaTextProvider_SetSelection_DoesntSelectText_IfIncorrectArguments(ComboBoxStyle dropDownStyle, int start, int end) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + provider.SetSelection(start, end); + UiaCore.ITextRangeProvider[] selection = provider.GetSelection(); + Assert.NotNull(selection); + + UiaTextRange textRange = selection[0] as UiaTextRange; + + Assert.NotNull(textRange); + Assert.Equal(0, textRange.Start); + Assert.Equal(0, textRange.End); + Assert.Equal(0, comboBox.SelectionStart); + Assert.Equal(0, comboBox.SelectionStart + comboBox.SelectionLength); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 0)] + [InlineData(ComboBoxStyle.DropDown, 2)] + [InlineData(ComboBoxStyle.Simple, 0)] + [InlineData(ComboBoxStyle.Simple, 2)] + public void ComboBoxUiaTextProvider_LineScroll_ReturnsFalse(ComboBoxStyle dropDownStyle, int newLine) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.CreateControl(); + comboBox.Items.Add("Some long long test text for testing GetFirstVisibleLine method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.LineScroll(0, newLine)); + Assert.Equal(0, provider.FirstVisibleLine); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 0)] + [InlineData(ComboBoxStyle.DropDown, 2)] + [InlineData(ComboBoxStyle.Simple, 0)] + [InlineData(ComboBoxStyle.Simple, 2)] + public void ComboBoxUiaTextProvider_LineScroll_DoesntWork_WitoutHandle(ComboBoxStyle dropDownStyle, int newLine) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.Items.Add("Some long long test text for testing GetFirstVisibleLine method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + Assert.False(provider.LineScroll(0, newLine)); + Assert.Equal(-1, provider.FirstVisibleLine); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetLogfont_ReturnSegoe_ByDefault(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + LOGFONTW logFont = provider.Logfont; + + string actual = logFont.FaceName.ToString(); + + Assert.Equal("Segoe UI", actual); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_GetLogfont_ReturnEmpty_WithoutHandle(ComboBoxStyle dropDownStyle) + { + using (new NoAssertContext()) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + Assert.False(comboBox.IsHandleCreated); + Assert.Null(comboBox.TestAccessor().Dynamic._childEdit); + Assert.Equal(new LOGFONTW(), provider.Logfont); + } + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_FirstVisibleLine_DefaultValueCorrect(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + var actualValie = (int)(long)SendMessageW(comboBox.TestAccessor().Dynamic._childEdit, (WM)EM.GETFIRSTVISIBLELINE); + + Assert.Equal(actualValie, provider.FirstVisibleLine); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown)] + [InlineData(ComboBoxStyle.Simple)] + public void ComboBoxUiaTextProvider_LinesCount_DefaultValueCorrect(ComboBoxStyle dropDownStyle) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.CreateControl(); + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + var actualValie = (int)(long)SendMessageW(comboBox.TestAccessor().Dynamic._childEdit, (WM)EM.GETLINECOUNT); + + Assert.Equal(actualValie, provider.LinesCount); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 0)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 50)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, 200)] + [InlineData(ComboBoxStyle.DropDown, 50, 20, -5)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 0)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 50)] + [InlineData(ComboBoxStyle.Simple, 50, 20, 200)] + [InlineData(ComboBoxStyle.Simple, 50, 20, -5)] + public void ComboBoxUiaTextProvider_GetLineFromCharIndex_DefaultValueCorrect( + ComboBoxStyle dropDownStyle, + int width, + int height, + int charIndex) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(width, height) }; + comboBox.CreateControl(); + comboBox.Items.Add("Some test text for testing GetLineFromCharIndex method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + int expectedLine = (int)(long)SendMessageW(comboBox.TestAccessor().Dynamic._childEdit, (WM)EM.LINEFROMCHAR, (IntPtr)charIndex); + int actualLine = provider.GetLineFromCharIndex(charIndex); + + Assert.Equal(expectedLine, actualLine); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + + [WinFormsTheory] + [InlineData(ComboBoxStyle.DropDown, 0)] + [InlineData(ComboBoxStyle.DropDown, 2)] + [InlineData(ComboBoxStyle.Simple, 0)] + [InlineData(ComboBoxStyle.Simple, 2)] + public void ComboBoxUiaTextProvider_LineScroll_DefaultValueCorrect(ComboBoxStyle dropDownStyle, int newLine) + { + using ComboBox comboBox = new ComboBox() { DropDownStyle = dropDownStyle, Size = new Size(50, 100) }; + comboBox.CreateControl(); + comboBox.Items.Add("Some long long test text for testing GetFirstVisibleLine method"); + comboBox.SelectedIndex = 0; + ComboBox.ComboBoxUiaTextProvider provider = new ComboBox.ComboBoxUiaTextProvider(comboBox); + + var expectedValue = SendMessageW(comboBox.TestAccessor().Dynamic._childEdit, (WM)EM.LINESCROLL, (IntPtr)0, (IntPtr)newLine) != IntPtr.Zero; + + Assert.Equal(expectedValue, provider.LineScroll(0, newLine)); + Assert.True(comboBox.IsHandleCreated); + Assert.NotNull(comboBox.TestAccessor().Dynamic._childEdit); + } + } +}