From f2baffc089fa196552649e4e1188327dc47bed8d Mon Sep 17 00:00:00 2001 From: Vladimir Krestov Date: Fri, 4 Sep 2020 11:22:03 +0300 Subject: [PATCH] Implement TextPattern support for Text controls - Implement base integration to AccessibleObject - Add TextPattern support for TextBox (singleline and multiline) and MaskedTextBox - Disable managed UiaTextProvider for RichTextBox to use native provider as the native Win32 RichEdit control already supports TextPattern. Return ControlAccessibleObject as AccessibilityInstance instead TextBoxBaseAccessibleObject (the latter supports managed UiaTextProvider). - Add tests Fixes #1588 --- .../src/PublicAPI.Unshipped.txt | 3 + .../System/Windows/Forms/AccessibleObject.cs | 81 +- .../Windows/Forms/InternalAccessibleObject.cs | 48 +- .../src/System/Windows/Forms/RichTextBox.cs | 4 +- .../src/System/Windows/Forms/TextBox.cs | 42 + ...TextBoxBase.TextBoxBaseAccessibleObject.cs | 46 + .../TextBoxBase.TextBoxBaseUiaTextProvider.cs | 381 ++++++++ .../src/System/Windows/Forms/TextBoxBase.cs | 14 +- .../TextBoxAccessibleObjectTests.cs | 37 + .../TextBoxBaseAccessibleObjectTests.cs | 103 ++ .../UpDownEditAccessibleObjectTests.cs | 30 + .../System/Windows/Forms/RichTextBoxTests.cs | 69 +- ...BoxBase.TextBoxBaseUiaTextProviderTests.cs | 923 ++++++++++++++++++ .../tests/UnitTests/TextBoxBaseTests.cs | 53 +- .../tests/UnitTests/TextBoxTests.cs | 6 +- 15 files changed, 1792 insertions(+), 48 deletions(-) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseAccessibleObject.cs create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxAccessibleObjectTests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxBaseAccessibleObjectTests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/UpDownEditAccessibleObjectTests.cs create mode 100644 src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProviderTests.cs diff --git a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt index e69de29bb2d..400a5b031fe 100644 --- a/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt +++ b/src/System.Windows.Forms/src/PublicAPI.Unshipped.txt @@ -0,0 +1,3 @@ +~override System.Windows.Forms.RichTextBox.CreateAccessibilityInstance() -> System.Windows.Forms.AccessibleObject +~override System.Windows.Forms.TextBox.OnKeyUp(System.Windows.Forms.KeyEventArgs e) -> void +~override System.Windows.Forms.TextBox.OnMouseDown(System.Windows.Forms.MouseEventArgs e) -> void \ No newline at end of file diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs index d28725f95ad..3bd8536bd84 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs @@ -43,7 +43,9 @@ public partial class AccessibleObject : UiaCore.ISelectionItemProvider, UiaCore.IRawElementProviderHwndOverride, UiaCore.IScrollItemProvider, - UiaCore.IMultipleViewProvider + UiaCore.IMultipleViewProvider, + UiaCore.ITextProvider, + UiaCore.ITextProvider2 { /// /// Specifies the interface used by this . @@ -64,6 +66,9 @@ public partial class AccessibleObject : // Indicates this object is being used ONLY to wrap a system IAccessible private readonly bool systemWrapper; + private UiaTextProvider? _textProvider; + private UiaTextProvider2? _textProvider2; + // The support for the UIA Notification event begins in RS3. // Assume the UIA Notification event is available until we learn otherwise. // If we learn that the UIA Notification event is not available, @@ -350,15 +355,13 @@ internal virtual int ProviderOptions internal virtual UiaCore.IRawElementProviderSimple? HostRawElementProvider => null; - internal virtual object? GetPropertyValue(UiaCore.UIA propertyID) - { - if (propertyID == UiaCore.UIA.IsInvokePatternAvailablePropertyId) + internal virtual object? GetPropertyValue(UiaCore.UIA propertyID) => + propertyID switch { - return IsInvokePatternAvailable; - } - - return null; - } + UiaCore.UIA.IsInvokePatternAvailablePropertyId => IsInvokePatternAvailable, + UiaCore.UIA.BoundingRectanglePropertyId => Bounds, + _ => null + }; private bool IsInvokePatternAvailable { @@ -474,6 +477,29 @@ internal virtual void Toggle() internal virtual void Invoke() => DoDefaultAction(); + internal virtual UiaCore.ITextRangeProvider? DocumentRangeInternal => _textProvider?.DocumentRange; + + internal virtual UiaCore.ITextRangeProvider[]? GetTextSelection() => _textProvider?.GetSelection(); + + internal virtual UiaCore.ITextRangeProvider[]? GetTextVisibleRanges() => _textProvider?.GetVisibleRanges(); + + internal virtual UiaCore.ITextRangeProvider? GetTextRangeFromChild(UiaCore.IRawElementProviderSimple childElement) + => _textProvider?.RangeFromChild(childElement); + + internal virtual UiaCore.ITextRangeProvider? GetTextRangeFromPoint(Point screenLocation) => _textProvider?.RangeFromPoint(screenLocation); + + internal virtual UiaCore.SupportedTextSelection SupportedTextSelectionInternal + => _textProvider?.SupportedTextSelection ?? UiaCore.SupportedTextSelection.None; + + internal virtual UiaCore.ITextRangeProvider? GetTextCaretRange(out BOOL isActive) + { + isActive = BOOL.FALSE; + return _textProvider2?.GetCaretRange(out isActive); + } + + internal virtual UiaCore.ITextRangeProvider? GetRangeFromAnnotation(UiaCore.IRawElementProviderSimple annotationElement) => + _textProvider2?.RangeFromAnnotation(annotationElement); + internal virtual bool IsReadOnly => false; internal virtual void SetValue(string? newValue) @@ -673,6 +699,37 @@ UiaCore.IRawElementProviderSimple[] UiaCore.ILegacyIAccessibleProvider.GetSelect void UiaCore.IInvokeProvider.Invoke() => Invoke(); + UiaCore.ITextRangeProvider? UiaCore.ITextProvider.DocumentRange => DocumentRangeInternal; + + UiaCore.ITextRangeProvider[]? UiaCore.ITextProvider.GetSelection() => GetTextSelection(); + + UiaCore.ITextRangeProvider[]? UiaCore.ITextProvider.GetVisibleRanges() => GetTextVisibleRanges(); + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider.RangeFromChild(UiaCore.IRawElementProviderSimple childElement) => + GetTextRangeFromChild(childElement); + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider.RangeFromPoint(Point screenLocation) => GetTextRangeFromPoint(screenLocation); + + UiaCore.SupportedTextSelection UiaCore.ITextProvider.SupportedTextSelection => SupportedTextSelectionInternal; + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider2.DocumentRange => DocumentRangeInternal; + + UiaCore.ITextRangeProvider[]? UiaCore.ITextProvider2.GetSelection() => GetTextSelection(); + + UiaCore.ITextRangeProvider[]? UiaCore.ITextProvider2.GetVisibleRanges() => GetTextVisibleRanges(); + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider2.RangeFromChild(UiaCore.IRawElementProviderSimple childElement) => + GetTextRangeFromChild(childElement); + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider2.RangeFromPoint(Point screenLocation) => GetTextRangeFromPoint(screenLocation); + + UiaCore.SupportedTextSelection UiaCore.ITextProvider2.SupportedTextSelection => SupportedTextSelectionInternal; + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider2.GetCaretRange(out BOOL isActive) => GetTextCaretRange(out isActive); + + UiaCore.ITextRangeProvider? UiaCore.ITextProvider2.RangeFromAnnotation(UiaCore.IRawElementProviderSimple annotationElement) => + GetRangeFromAnnotation(annotationElement); + BOOL UiaCore.IValueProvider.IsReadOnly => IsReadOnly ? BOOL.TRUE : BOOL.FALSE; string? UiaCore.IValueProvider.Value => Value; @@ -1595,6 +1652,12 @@ protected void UseStdAccessibleObjects(IntPtr handle, int objid) } } + internal void UseTextProviders(UiaTextProvider textProvider, UiaTextProvider2 textProvider2) + { + _textProvider = textProvider ?? throw new ArgumentNullException(nameof(textProvider)); + _textProvider2 = textProvider2 ?? throw new ArgumentNullException(nameof(textProvider2)); + } + /// /// Performs custom navigation between parent/child/sibling accessible /// objects. This is basically just a wrapper for GetSysChild(), that diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs index dedf92aba1b..97ea7e55874 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/InternalAccessibleObject.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics.CodeAnalysis; +using System.Drawing; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; @@ -40,7 +41,9 @@ internal sealed class InternalAccessibleObject : ISelectionItemProvider, IScrollItemProvider, IRawElementProviderHwndOverride, - IMultipleViewProvider + IMultipleViewProvider, + ITextProvider, + ITextProvider2 { private IAccessible publicIAccessible; // AccessibleObject as IAccessible private readonly Oleaut32.IEnumVariant publicIEnumVariant; // AccessibleObject as Oleaut32.IEnumVariant @@ -69,6 +72,8 @@ internal sealed class InternalAccessibleObject : private readonly IScrollItemProvider publicIScrollItemProvider; // AccessibleObject as IScrollItemProvider private readonly IRawElementProviderHwndOverride publicIRawElementProviderHwndOverride; // AccessibleObject as IRawElementProviderHwndOverride private readonly IMultipleViewProvider publicIMultiViewProvider; // AccessibleObject as IMultipleViewProvider + private readonly ITextProvider publicITextProvider; // AccessibleObject as ITextProvider + private readonly ITextProvider2 publicITextProvider2; // AccessibleObject as ITextProvider2 /// /// Create a new wrapper. @@ -100,6 +105,8 @@ internal InternalAccessibleObject(AccessibleObject accessibleImplemention) publicIScrollItemProvider = (IScrollItemProvider)accessibleImplemention; publicIRawElementProviderHwndOverride = (IRawElementProviderHwndOverride)accessibleImplemention; publicIMultiViewProvider = (IMultipleViewProvider)accessibleImplemention; + publicITextProvider = (ITextProvider)accessibleImplemention; + publicITextProvider2 = (ITextProvider2)accessibleImplemention; // Note: Deliberately not holding onto AccessibleObject to enforce all access through the interfaces } @@ -314,6 +321,8 @@ ProviderOptions IRawElementProviderSimple.ProviderOptions UIA.SelectionItemPatternId => (ISelectionItemProvider)this, UIA.ScrollItemPatternId => (IScrollItemProvider)this, UIA.MultipleViewPatternId => (IMultipleViewProvider)this, + UIA.TextPatternId => (ITextProvider)this, + UIA.TextPattern2Id => (ITextProvider2)this, _ => null }; } @@ -365,8 +374,7 @@ UiaRect IRawElementProviderFragment.BoundingRectangle void ILegacyIAccessibleProvider.DoDefaultAction() => publicILegacyIAccessibleProvider.DoDefaultAction(); - IAccessible? ILegacyIAccessibleProvider.GetIAccessible() - => publicILegacyIAccessibleProvider.GetIAccessible(); + IAccessible? ILegacyIAccessibleProvider.GetIAccessible() => publicILegacyIAccessibleProvider.GetIAccessible(); IRawElementProviderSimple[] ILegacyIAccessibleProvider.GetSelection() => publicILegacyIAccessibleProvider.GetSelection(); @@ -376,6 +384,40 @@ UiaRect IRawElementProviderFragment.BoundingRectangle void IInvokeProvider.Invoke() => publicIInvokeProvider.Invoke(); + ITextRangeProvider[]? ITextProvider.GetSelection() => publicITextProvider.GetSelection(); + + ITextRangeProvider[]? ITextProvider.GetVisibleRanges() => publicITextProvider.GetVisibleRanges(); + + ITextRangeProvider? ITextProvider.RangeFromChild(IRawElementProviderSimple childElement) + => publicITextProvider.RangeFromChild(childElement); + + ITextRangeProvider? ITextProvider.RangeFromPoint(Point screenLocation) + => publicITextProvider.RangeFromPoint(screenLocation); + + SupportedTextSelection ITextProvider.SupportedTextSelection => publicITextProvider.SupportedTextSelection; + + ITextRangeProvider? ITextProvider.DocumentRange => publicITextProvider.DocumentRange; + + ITextRangeProvider[]? ITextProvider2.GetSelection() => publicITextProvider2.GetSelection(); + + ITextRangeProvider[]? ITextProvider2.GetVisibleRanges() => publicITextProvider2.GetVisibleRanges(); + + ITextRangeProvider? ITextProvider2.RangeFromChild(IRawElementProviderSimple childElement) + => publicITextProvider2.RangeFromChild(childElement); + + ITextRangeProvider? ITextProvider2.RangeFromPoint(Point screenLocation) + => publicITextProvider2.RangeFromPoint(screenLocation); + + SupportedTextSelection ITextProvider2.SupportedTextSelection => publicITextProvider2.SupportedTextSelection; + + ITextRangeProvider? ITextProvider2.DocumentRange => publicITextProvider2.DocumentRange; + + ITextRangeProvider? ITextProvider2.GetCaretRange(out BOOL isActive) + => publicITextProvider2.GetCaretRange(out isActive); + + ITextRangeProvider? ITextProvider2.RangeFromAnnotation(IRawElementProviderSimple annotationElement) + => publicITextProvider2.RangeFromAnnotation(annotationElement); + BOOL IValueProvider.IsReadOnly => publicIValueProvider.IsReadOnly; string? IValueProvider.Value => publicIValueProvider.Value; diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs index 8706f2ee944..a76031139ba 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/RichTextBox.cs @@ -2257,7 +2257,7 @@ private RichTextBoxSelectionAttribute GetCharFormat(CFM mask, CFE effect) return charFormat; } - Font GetCharFormatFont(bool selectionOnly) + private Font GetCharFormatFont(bool selectionOnly) { ForceHandleCreate(); @@ -3237,6 +3237,8 @@ private void UserPreferenceChangedHandler(object o, UserPreferenceChangedEventAr } } + protected override AccessibleObject CreateAccessibilityInstance() => new ControlAccessibleObject(this); + /// /// Creates the IRichEditOleCallback compatible object for handling RichEdit callbacks. For more /// information look up the MSDN info on this interface. This is designed to be a back door of diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs index a2b02a0c540..433b16d8bb0 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs @@ -647,6 +647,48 @@ protected override void OnHandleDestroyed(EventArgs e) base.OnHandleDestroyed(e); } + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + + if (IsHandleCreated && ContainsNavigationKeyCode(e.KeyCode)) + { + AccessibilityObject?.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); + } + } + + private bool ContainsNavigationKeyCode(Keys keyCode) + { + switch (keyCode) + { + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Home: + case Keys.End: + case Keys.Left: + case Keys.Right: + return true; + default: + return false; + } + } + + protected override void OnMouseDown(MouseEventArgs e) + { + base.OnMouseDown(e); + + if (IsHandleCreated) + { + // As there is no corresponding windows notification + // about text selection changed for TextBox assuming + // that any mouse down on textbox leads to change of + // the caret position and thereby change the selection. + AccessibilityObject?.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); + } + } + protected virtual void OnTextAlignChanged(EventArgs e) { if (Events[EVENT_TEXTALIGNCHANGED] is EventHandler eh) diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseAccessibleObject.cs new file mode 100644 index 00000000000..dc40f5b967c --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseAccessibleObject.cs @@ -0,0 +1,46 @@ +// 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 static Interop; + +namespace System.Windows.Forms +{ + public abstract partial class TextBoxBase + { + internal class TextBoxBaseAccessibleObject : ControlAccessibleObject + { + private readonly TextBoxBase _owningTextBoxBase; + private readonly TextBoxBaseUiaTextProvider _textProvider; + + public TextBoxBaseAccessibleObject(TextBoxBase owner) : base(owner) + { + _owningTextBoxBase = owner; + _textProvider = new TextBoxBaseUiaTextProvider(owner); + + UseTextProviders(_textProvider, _textProvider); + } + + internal override bool IsIAccessibleExSupported() => true; + + internal override bool IsPatternSupported(UiaCore.UIA patternId) => + patternId switch + { + UiaCore.UIA.TextPatternId => true, + UiaCore.UIA.TextPattern2Id => true, + _ => base.IsPatternSupported(patternId) + }; + + internal override object? GetPropertyValue(UiaCore.UIA propertyID) => + propertyID switch + { + UiaCore.UIA.IsTextPatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.TextPatternId), + UiaCore.UIA.IsTextPattern2AvailablePropertyId => IsPatternSupported(UiaCore.UIA.TextPattern2Id), + UiaCore.UIA.IsValuePatternAvailablePropertyId => IsPatternSupported(UiaCore.UIA.ValuePatternId), + _ => base.GetPropertyValue(propertyID) + }; + + internal override bool IsReadOnly => _owningTextBoxBase.ReadOnly; + } + } +} 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 new file mode 100644 index 00000000000..6deaf4b1061 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProvider.cs @@ -0,0 +1,381 @@ +// 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 abstract partial class TextBoxBase + { + internal class TextBoxBaseUiaTextProvider : UiaTextProvider2 + { + private readonly TextBoxBase _owningTextBoxBase; + + public TextBoxBaseUiaTextProvider(TextBoxBase owner) + { + _owningTextBoxBase = owner ?? throw new ArgumentNullException(nameof(owner)); + } + + public override UiaCore.ITextRangeProvider[]? GetSelection() + { + if (!_owningTextBoxBase.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 a 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); + + var internalAccessibleObject = new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject); + return new UiaCore.ITextRangeProvider[] { new UiaTextRange(internalAccessibleObject, this, start, end) }; + } + + public override UiaCore.ITextRangeProvider[]? GetVisibleRanges() + { + if (!_owningTextBoxBase.IsHandleCreated) + { + return null; + } + + GetVisibleRangePoints(out int start, out int end); + var internalAccessibleObject = new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject); + + return new UiaCore.ITextRangeProvider[] { new UiaTextRange(internalAccessibleObject, this, start, end) }; + } + + 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 (!_owningTextBoxBase.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(new HandleRef(null, IntPtr.Zero), new HandleRef(this, _owningTextBoxBase.Handle), ref clientLocation, 1) == 0) + { + return new UiaTextRange(new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject), 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 = _owningTextBoxBase.ClientRectangle; + + 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 = _owningTextBoxBase.GetCharIndexFromPosition(clientLocation); + + return new UiaTextRange(new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject), this, start, start); + } + + public override UiaCore.ITextRangeProvider DocumentRange => new UiaTextRange(new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject), this, 0, TextLength); + + public override UiaCore.SupportedTextSelection SupportedTextSelection => UiaCore.SupportedTextSelection.Single; + + public override UiaCore.ITextRangeProvider? GetCaretRange(out BOOL isActive) + { + isActive = BOOL.FALSE; + + if (!_owningTextBoxBase.IsHandleCreated) + { + return null; + } + + var hasKeyboardFocus = _owningTextBoxBase.AccessibilityObject.GetPropertyValue(UiaCore.UIA.HasKeyboardFocusPropertyId); + if (hasKeyboardFocus is bool && (bool)hasKeyboardFocus) + { + isActive = BOOL.TRUE; + } + + var internalAccessibleObject = new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject); + + return new UiaTextRange(internalAccessibleObject, this, _owningTextBoxBase.SelectionStart, _owningTextBoxBase.SelectionStart); + } + + 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. + /// + /// + /// 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) + { + var internalAccessibleObject = new InternalAccessibleObject(_owningTextBoxBase.AccessibilityObject); + + return new UiaTextRange(internalAccessibleObject, this, 0, 0); + } + + public override Rectangle BoundingRectangle + => _owningTextBoxBase.IsHandleCreated + ? (Rectangle)GetFormattingRectangle() + : Rectangle.Empty; + + public override int FirstVisibleLine + => _owningTextBoxBase.IsHandleCreated + ? (int)(long)SendMessageW(_owningTextBoxBase, (WM)EM.GETFIRSTVISIBLELINE) + : -1; + + public override bool IsMultiline => _owningTextBoxBase.Multiline; + + public override bool IsReadingRTL + => _owningTextBoxBase.IsHandleCreated + ? WindowExStyle.HasFlag(WS_EX.RTLREADING) + : false; + + public override bool IsReadOnly => _owningTextBoxBase.ReadOnly; + + public override bool IsScrollable + { + get + { + if (!_owningTextBoxBase.IsHandleCreated) + { + return false; + } + + ES extendedStyle = (ES)(long)GetWindowLong(_owningTextBoxBase, GWL.STYLE); + return extendedStyle.HasFlag(ES.AUTOHSCROLL) || extendedStyle.HasFlag(ES.AUTOVSCROLL); + } + } + + public override int LinesCount + => _owningTextBoxBase.IsHandleCreated + ? (int)(long)SendMessageW(new HandleRef(this, _owningTextBoxBase.Handle), (WM)EM.GETLINECOUNT) + : -1; + + public override int LinesPerPage + { + get + { + if (!_owningTextBoxBase.IsHandleCreated) + { + return -1; + } + + Rectangle rect = _owningTextBoxBase.ClientRectangle; + if (rect.IsEmpty) + { + return 0; + } + + if (!_owningTextBoxBase.Multiline) + { + return 1; + } + + int fontHeight = _owningTextBoxBase.Font.Height; + return fontHeight != 0 ? (int)Math.Ceiling(((double)rect.Height) / fontHeight) : 0; + } + } + + public override LOGFONTW Logfont + => _owningTextBoxBase.IsHandleCreated + ? LOGFONTW.FromFont(_owningTextBoxBase.Font) + : default; + + public override string Text + => _owningTextBoxBase.IsHandleCreated + ? _owningTextBoxBase.Text + : string.Empty; + + public override int TextLength + => _owningTextBoxBase.IsHandleCreated + ? (int)(long)SendMessageW(_owningTextBoxBase, WM.GETTEXTLENGTH) + : -1; + + public override WS_EX WindowExStyle + => _owningTextBoxBase.IsHandleCreated + ? GetWindowExStyle(_owningTextBoxBase.Handle) + : WS_EX.LEFT; + + public override WS WindowStyle + => _owningTextBoxBase.IsHandleCreated + ? GetWindowStyle(_owningTextBoxBase.Handle) + : WS.OVERLAPPED; + + public override ES EditStyle + => _owningTextBoxBase.IsHandleCreated + ? GetEditStyle(_owningTextBoxBase.Handle) + : ES.LEFT; + + public override int GetLineFromCharIndex(int charIndex) + => _owningTextBoxBase.IsHandleCreated + ? _owningTextBoxBase.GetLineFromCharIndex(charIndex) + : -1; + + public override int GetLineIndex(int line) + => _owningTextBoxBase.IsHandleCreated + ? (int)(long)SendMessageW(_owningTextBoxBase, (WM)EM.LINEINDEX, (IntPtr)line) + : -1; + + public override Point GetPositionFromChar(int charIndex) + => _owningTextBoxBase.IsHandleCreated + ? _owningTextBoxBase.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 (!_owningTextBoxBase.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 _owningTextBoxBase.GetPositionFromCharIndex(useNext ? startCharIndex + 1 : startCharIndex); + } + + pt = _owningTextBoxBase.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 = _owningTextBoxBase.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 void GetVisibleRangePoints(out int visibleStart, out int visibleEnd) + { + visibleStart = 0; + visibleEnd = 0; + + if (!_owningTextBoxBase.IsHandleCreated || IsDegenerate(_owningTextBoxBase.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 = _owningTextBoxBase.GetCharIndexFromPosition(ptStart); + visibleEnd = _owningTextBoxBase.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 bool LineScroll(int charactersHorizontal, int linesVertical) + // Sends an EM_LINESCROLL message to scroll it horizontally and/or vertically. + => _owningTextBoxBase.IsHandleCreated + && SendMessageW(_owningTextBoxBase, (WM)EM.LINESCROLL, (IntPtr)charactersHorizontal, (IntPtr)linesVertical) != IntPtr.Zero; + + public override void SetSelection(int start, int end) + { + if (!_owningTextBoxBase.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(_owningTextBoxBase, (WM)EM.SETSEL, (IntPtr)start, (IntPtr)end); + } + + private RECT GetFormattingRectangle() + { + Debug.Assert(_owningTextBoxBase.IsHandleCreated); + + // Send an EM_GETRECT message to find out the bounding rectangle. + RECT rectangle = new RECT(); + SendMessageW(_owningTextBoxBase, (WM)EM.GETRECT, (IntPtr)0, ref rectangle); + return rectangle; + } + + private bool GetTextExtentPoint32(char item, out Size size) + { + Debug.Assert(_owningTextBoxBase.IsHandleCreated); + + size = new Size(); + + using var hdc = new GetDcScope(_owningTextBoxBase.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/TextBoxBase.cs b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs index 315cbf7654b..0fdddba4e5e 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/TextBoxBase.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -25,7 +25,7 @@ namespace System.Windows.Forms [DefaultEvent(nameof(TextChanged))] [DefaultBindingProperty(nameof(Text))] [Designer("System.Windows.Forms.Design.TextBoxBaseDesigner, " + AssemblyRef.SystemDesign)] - public abstract class TextBoxBase : Control + public abstract partial class TextBoxBase : Control { // The boolean properties for this control are contained in the textBoxFlags bit // vector. We can store up to 32 boolean values in this one vector. Here we @@ -1124,6 +1124,7 @@ public int SelectionStart { throw new ArgumentOutOfRangeException(nameof(value), value, string.Format(SR.InvalidArgument, nameof(SelectionStart), value)); } + Select(value, SelectionLength); } } @@ -1364,6 +1365,8 @@ public void ClearUndo() /// public void Copy() => SendMessageW(this, WM.COPY); + protected override AccessibleObject CreateAccessibilityInstance() => new TextBoxBaseAccessibleObject(this); + protected override void CreateHandle() { // This "creatingHandle" stuff is to avoid property change events @@ -1653,7 +1656,7 @@ public virtual int GetCharIndexFromPosition(Point pt) /// public virtual int GetLineFromCharIndex(int index) { - return unchecked((int)(long)SendMessageW(this, (WM)EM.LINEFROMCHAR, (IntPtr)index)); + return (int)(long)SendMessageW(this, (WM)EM.LINEFROMCHAR, (IntPtr)index); } /// @@ -1666,7 +1669,7 @@ public virtual Point GetPositionFromCharIndex(int index) return Point.Empty; } - int i = (int)User32.SendMessageW(this, (WM)EM.POSFROMCHAR, (IntPtr)index); + int i = (int)(long)SendMessageW(this, (WM)EM.POSFROMCHAR, (IntPtr)index); return new Point(PARAM.SignedLOWORD(i), PARAM.SignedHIWORD(i)); } @@ -1812,6 +1815,7 @@ public void Select(int start, int length) { length = (int)longLength; } + start = textLen; } @@ -1834,6 +1838,8 @@ private protected virtual void SelectInternal(int start, int length, int textLen AdjustSelectionStartAndEnd(start, length, out int s, out int e, textLen); SendMessageW(this, (WM)EM.SETSEL, (IntPtr)s, (IntPtr)e); + + AccessibilityObject?.RaiseAutomationEvent(UiaCore.UIA.Text_TextSelectionChangedEventId); } else { diff --git a/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxAccessibleObjectTests.cs new file mode 100644 index 00000000000..d9f0faf0c5e --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxAccessibleObjectTests.cs @@ -0,0 +1,37 @@ +// 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 Xunit; + +namespace System.Windows.Forms.Tests.AccessibleObjects +{ + public class TextBoxAccessibleObjectTests + { + [WinFormsTheory] + [InlineData((int)Interop.UiaCore.UIA.IsTextPatternAvailablePropertyId)] + [InlineData((int)Interop.UiaCore.UIA.IsTextPattern2AvailablePropertyId)] + public void TextBoxAccessibleObject_TextPatternAvailable(int propertyId) + { + using TextBox textBox = new TextBox(); + AccessibleObject textBoxAccessibleObject = textBox.AccessibilityObject; + + // Interop.UiaCore.UIA accessible level (internal) is less than the test level (public) so it needs boxing and unboxing + Assert.True((bool)textBoxAccessibleObject.GetPropertyValue((Interop.UiaCore.UIA)propertyId)); + Assert.False(textBox.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData((int)Interop.UiaCore.UIA.TextPatternId)] + [InlineData((int)Interop.UiaCore.UIA.TextPattern2Id)] + public void TextBoxAccessibleObject_TextPatternSupported(int patternId) + { + using TextBox textBox = new TextBox(); + AccessibleObject textBoxAccessibleObject = textBox.AccessibilityObject; + + // Interop.UiaCore.UIA accessible level (internal) is less than the test level (public) so it needs boxing and unboxing + Assert.True(textBoxAccessibleObject.IsPatternSupported((Interop.UiaCore.UIA)patternId)); + Assert.False(textBox.IsHandleCreated); + } + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxBaseAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxBaseAccessibleObjectTests.cs new file mode 100644 index 00000000000..1a286bb35e2 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/TextBoxBaseAccessibleObjectTests.cs @@ -0,0 +1,103 @@ +// 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 Xunit; + +namespace System.Windows.Forms.Tests.AccessibleObjects +{ + public class TextBoxBaseAccessibleObjectTests + { + [WinFormsFact] + public void TextBoxBaseAccessibleObject_ctor_default() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + + AccessibleObject textBoxAccessibleObject = textBoxBase.AccessibilityObject; + Assert.NotNull(textBoxAccessibleObject); + + TextBoxBase.TextBoxBaseAccessibleObject textBoxBaseAccessibleObject = new TextBoxBase.TextBoxBaseAccessibleObject(textBoxBase); + Assert.NotNull(textBoxBaseAccessibleObject); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData((int)Interop.UiaCore.UIA.IsTextPatternAvailablePropertyId)] + [InlineData((int)Interop.UiaCore.UIA.IsTextPattern2AvailablePropertyId)] + public void TextBoxBaseAccessibleObject_TextPatternAvailable(int propertyId) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + AccessibleObject textBoxAccessibleObject = textBoxBase.AccessibilityObject; + + // Interop.UiaCore.UIA accessible level (internal) is less than the test level (public) so it needs boxing and unboxing + Assert.True((bool)textBoxAccessibleObject.GetPropertyValue((Interop.UiaCore.UIA)propertyId)); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData((int)Interop.UiaCore.UIA.TextPatternId)] + [InlineData((int)Interop.UiaCore.UIA.TextPattern2Id)] + public void TextBoxBaseAccessibleObject_TextPatternSupported(int patternId) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + AccessibleObject textBoxAccessibleObject = textBoxBase.AccessibilityObject; + + // Interop.UiaCore.UIA accessible level (internal) is less than the test level (public) so it needs boxing and unboxing + Assert.True(textBoxAccessibleObject.IsPatternSupported((Interop.UiaCore.UIA)patternId)); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(true)] + [InlineData(false)] + public void TextBoxBaseAccessibleObject_IsReadOnly_ReturnsCorrectValue(bool readOnly) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + AccessibleObject accessibleObject = textBoxBase.AccessibilityObject; + + textBoxBase.ReadOnly = readOnly; + Assert.Equal(textBoxBase.ReadOnly, accessibleObject.IsReadOnly); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseAccessibleObject_Value_ReturnsEmpty_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Text = "Some test text"; + AccessibleObject accessibleObject = textBoxBase.AccessibilityObject; + Assert.Equal(string.Empty, accessibleObject.Value); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseAccessibleObject_Value_EqualsText() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + textBoxBase.Text = "Some test text"; + AccessibleObject accessibleObject = textBoxBase.AccessibilityObject; + Assert.Equal(textBoxBase.Text, accessibleObject.Value); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(50, 20)] + [InlineData(100, 10)] + public void TextBoxBaseAccessibleObject_BoundingRectangle_IsCorrect(int width, int height) + { + using TextBoxBase textBoxBase = new SubTextBoxBase { Size = new Size(width, height) }; + textBoxBase.CreateControl(); + AccessibleObject accessibleObject = textBoxBase.AccessibilityObject; + Rectangle expected = textBoxBase.RectangleToScreen(textBoxBase.ClientRectangle); // Forces Handle creating + Rectangle actual = accessibleObject.BoundingRectangle; + Assert.Equal(expected, actual); + Assert.True(textBoxBase.IsHandleCreated); + } + + private class SubTextBoxBase : TextBoxBase + { } + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/UpDownEditAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/UpDownEditAccessibleObjectTests.cs new file mode 100644 index 00000000000..43d38c58ea0 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/UpDownEditAccessibleObjectTests.cs @@ -0,0 +1,30 @@ +// 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 Xunit; +using static Interop.UiaCore; + +namespace System.Windows.Forms.Tests.AccessibleObjects +{ + public class UpDownEditAccessibleObjectTests + { + [WinFormsFact] + public void UpDownEditAccessibleObject_ctor_default() + { + using UpDownBase upDown = new SubUpDownBase(); + using UpDownBase.UpDownEdit upDownEdit = new UpDownBase.UpDownEdit(upDown); + Assert.NotNull(upDownEdit.AccessibilityObject); + Assert.False(upDown.IsHandleCreated); + } + + private class SubUpDownBase : UpDownBase + { + public override void DownButton() { } + + public override void UpButton() { } + + protected override void UpdateEditText() { } + } + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs index b4b8565ca6c..be34407fc63 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/RichTextBoxTests.cs @@ -12,6 +12,7 @@ using System.Threading; using WinForms.Common.Tests; using Xunit; +using Xunit.Abstractions; using static Interop; using static Interop.Richedit; using static Interop.User32; @@ -1604,40 +1605,56 @@ public void RichTextBox_Font_SetWithNonNullOldValueWithTextWithHandle_GetReturns public static IEnumerable Font_GetCharFormat_TestData() { - yield return new object[] { new Font("Arial", 8.25f), 165, 0 }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Bold), 165, CFE.BOLD }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Italic), 165, CFE.ITALIC }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Strikeout), 165, CFE.STRIKEOUT }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Underline), 165, CFE.UNDERLINE }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Bold | FontStyle.Italic | FontStyle.Regular | FontStyle.Strikeout | FontStyle.Underline, GraphicsUnit.Point, 10), 165, CFE.BOLD | CFE.ITALIC | CFE.UNDERLINE | CFE.STRIKEOUT }; + yield return new object[] { "Arial", 8.25f, FontStyle.Regular, GraphicsUnit.Point, 1, 165, 0 }; + yield return new object[] { "Arial", 8.25f, FontStyle.Bold, GraphicsUnit.Point, 1, 165, CFE.BOLD }; + yield return new object[] { "Arial", 8.25f, FontStyle.Italic, GraphicsUnit.Point, 1, 165, CFE.ITALIC }; + yield return new object[] { "Arial", 8.25f, FontStyle.Strikeout, GraphicsUnit.Point, 1, 165, CFE.STRIKEOUT }; + yield return new object[] { "Arial", 8.25f, FontStyle.Underline, GraphicsUnit.Point, 1, 165, CFE.UNDERLINE }; + yield return new object[] { "Arial", 8.25f, FontStyle.Bold | FontStyle.Italic | FontStyle.Regular | FontStyle.Strikeout | FontStyle.Underline, GraphicsUnit.Point, 10, 165, CFE.BOLD | CFE.ITALIC | CFE.UNDERLINE | CFE.STRIKEOUT }; } [WinFormsTheory] [MemberData(nameof(Font_GetCharFormat_TestData))] - public unsafe void RichTextBox_Font_GetCharFormat_Success(Font value, int expectedYHeight, int expectedEffects) + public unsafe void RichTextBox_Font_GetCharFormat_Success(string familyName, float emSize, FontStyle style, GraphicsUnit unit, byte gdiCharSet, int expectedYHeight, int expectedEffects) { using var control = new RichTextBox(); - Assert.NotEqual(IntPtr.Zero, control.Handle); - control.Font = value; + var format = new CHARFORMAT2W { cbSize = (uint)sizeof(CHARFORMAT2W), dwMask = (CFM)int.MaxValue }; - Assert.NotEqual(IntPtr.Zero, SendMessageW(control.Handle, (WM)Richedit.EM.GETCHARFORMAT, (IntPtr)SCF.ALL, ref format)); - Assert.Equal("Arial", format.FaceName.ToString()); - Assert.Equal(expectedYHeight, (int)format.yHeight); - Assert.Equal(CFE.AUTOBACKCOLOR | CFE.AUTOCOLOR | (CFE)expectedEffects, format.dwEffects); - Assert.Equal(0, format.bPitchAndFamily); - // Set null. - control.Font = null; - Assert.NotEqual(IntPtr.Zero, SendMessageW(control.Handle, (WM)Richedit.EM.GETCHARFORMAT, (IntPtr)SCF.ALL, ref format)); - Assert.Equal(Control.DefaultFont.Name, format.FaceName.ToString()); - Assert.Equal((int)(Control.DefaultFont.SizeInPoints * 20), (int)format.yHeight); - Assert.Equal(CFE.AUTOBACKCOLOR | CFE.AUTOCOLOR, format.dwEffects); - Assert.Equal(0, format.bPitchAndFamily); + IntPtr result; + + using (var font = new Font(familyName, emSize, style, unit, gdiCharSet)) + { + control.Font = font; + result = SendMessageW(control.Handle, (WM)Richedit.EM.GETCHARFORMAT, (IntPtr)SCF.ALL, ref format); + Assert.NotEqual(IntPtr.Zero, result); + Assert.Equal(familyName, format.FaceName.ToString()); + Assert.Equal(expectedYHeight, format.yHeight); + Assert.Equal(CFE.AUTOBACKCOLOR | CFE.AUTOCOLOR | (CFE)expectedEffects, format.dwEffects); + Assert.Equal(0, format.bPitchAndFamily); + + // Set null. + control.Font = null; + } + + var format1 = new CHARFORMAT2W + { + cbSize = (uint)sizeof(CHARFORMAT2W), + dwMask = (CFM)int.MaxValue + }; + + result = SendMessageW(control.Handle, (WM)Richedit.EM.GETCHARFORMAT, (IntPtr)SCF.ALL, ref format1); + Assert.NotEqual(IntPtr.Zero, result); + Assert.Equal(Control.DefaultFont.Name, format1.FaceName.ToString()); + Assert.Equal((int)(Control.DefaultFont.SizeInPoints * 20), (int)format1.yHeight); + Assert.True(format1.dwEffects.HasFlag(CFE.AUTOBACKCOLOR)); + Assert.True(format1.dwEffects.HasFlag(CFE.AUTOCOLOR)); + Assert.Equal(0, format1.bPitchAndFamily); } [WinFormsFact] @@ -4818,14 +4835,15 @@ public void RichTextBox_SelectionFont_GetDisposed_ThrowsObjectDisposedException( public static IEnumerable SelectionFont_Set_TestData() { - yield return new object[] { new Font("Arial", 8.25f), 1 }; - yield return new object[] { new Font("Arial", 8.25f, FontStyle.Bold | FontStyle.Italic | FontStyle.Regular | FontStyle.Strikeout | FontStyle.Underline, GraphicsUnit.Point, 10), 1 }; + yield return new object[] { "Arial", 8.25f, false, FontStyle.Regular, 0, 0, 1 }; + yield return new object[] { "Arial", 8.25f, true, FontStyle.Bold | FontStyle.Italic | FontStyle.Regular | FontStyle.Strikeout | FontStyle.Underline, GraphicsUnit.Point, 10, 1 }; } [WinFormsTheory] [MemberData(nameof(SelectionFont_Set_TestData))] - public void RichTextBox_SelectionFont_Set_GetReturnsExpected(Font value, byte expectedGdiCharset) + public void RichTextBox_SelectionFont_Set_GetReturnsExpected(string fontName, float fontSize, bool hasStyle, FontStyle fontStyle, GraphicsUnit units, byte gdiCharSet, byte expectedGdiCharset) { + using Font value = hasStyle ? new Font(fontName, fontSize, fontStyle, units, gdiCharSet) : new Font(fontName, fontSize); using var control = new RichTextBox { SelectionFont = value @@ -4850,8 +4868,9 @@ public void RichTextBox_SelectionFont_Set_GetReturnsExpected(Font value, byte ex [WinFormsTheory] [MemberData(nameof(SelectionFont_Set_TestData))] - public void RichTextBox_SelectionFont_SetWithHandle_GetReturnsExpected(Font value, byte expectedGdiCharset) + public void RichTextBox_SelectionFont_SetWithHandle_GetReturnsExpected(string fontName, float fontSize, bool hasStyle, FontStyle fontStyle, GraphicsUnit units, byte gdiCharSet, byte expectedGdiCharset) { + using Font value = hasStyle ? new Font(fontName, fontSize, fontStyle, units, gdiCharSet) : new Font(fontName, fontSize); using var control = new RichTextBox(); Assert.NotEqual(IntPtr.Zero, control.Handle); int invalidatedCallCount = 0; diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProviderTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProviderTests.cs new file mode 100644 index 00000000000..179bd142ef2 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/TextBoxBase.TextBoxBaseUiaTextProviderTests.cs @@ -0,0 +1,923 @@ +// 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 Xunit.Abstractions; +using static System.Windows.Forms.TextBoxBase; +using static Interop; +using static Interop.Gdi32; +using static Interop.User32; + +namespace System.Windows.Forms.Tests +{ + public class TextBoxBase_TextBoxBaseUiaTextProviderTests : IClassFixture + { + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_ctor_DoesntCreateControlHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + Assert.False(textBoxBase.IsHandleCreated); + + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_Ctor_ThrowsException_IfOwnerIsNull() + { + Assert.Throws(() => new TextBoxBaseUiaTextProvider(null)); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_IsMultiline_IsCorrect() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + textBoxBase.Multiline = false; + Assert.False(provider.IsMultiline); + + textBoxBase.Multiline = true; + Assert.True(provider.IsMultiline); + + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(true)] + [InlineData(false)] + public void TextBoxBaseUiaTextProvider_IsReadOnly_IsCorrect(bool readOnly) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + textBoxBase.ReadOnly = readOnly; + Assert.Equal(readOnly, provider.IsReadOnly); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_IsScrollable_IsCorrect() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.True(provider.IsScrollable); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_IsScrollable_False_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.False(provider.IsScrollable); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseTextProvider_GetWindowStyle_ReturnsNoneForNotInitializedControl() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.Equal(WS.OVERLAPPED, provider.WindowStyle); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(RightToLeft.Yes, true)] + [InlineData(RightToLeft.No, false)] + public void TextBoxBaseUiaTextProvider_IsReadingRTL_ReturnsCorrectValue(RightToLeft rightToLeft, bool expectedResult) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + textBoxBase.RightToLeft = rightToLeft; + + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.Equal(expectedResult, provider.IsReadingRTL); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(RightToLeft.Yes)] + [InlineData(RightToLeft.No)] + public void TextBoxBaseUiaTextProvider_IsReadingRTL_ReturnsFalse_WithoutHandle(RightToLeft rightToLeft) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.RightToLeft = rightToLeft; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.False(provider.IsReadingRTL); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_DocumentRange_IsNotNull() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.NotNull(provider.DocumentRange); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_SupportedTextSelection_IsNotNull() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + UiaCore.SupportedTextSelection uiaTextRange = provider.SupportedTextSelection; + Assert.Equal(UiaCore.SupportedTextSelection.Single, uiaTextRange); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetCaretRange_IsNotNull() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + UiaCore.ITextRangeProvider uiaTextRange = provider.GetCaretRange(out _); + Assert.NotNull(uiaTextRange); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetCaretRange_IsNull_IfHandleIsNotCreated() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + UiaCore.ITextRangeProvider uiaTextRange = provider.GetCaretRange(out _); + Assert.Null(uiaTextRange); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(30, 0, 0)] + [InlineData(30, 19, 1)] // Only 1 lines are placed at a height equal to 19 + [InlineData(30, 50, 3)] // Only 3 lines are placed at a height equal to 50 + [InlineData(30, 100, 6)] // Only 6 lines are placed at a height equal to 100 + public void TextBoxBaseUiaTextProvider_LinesPerPage_IsCorrect_with_handle(int width, int height, int lines) + { + using TextBoxBase textBoxBase = new SubTextBoxBase { Size = new Size(width, height) }; + textBoxBase.CreateControl(); + Assert.True(textBoxBase.IsHandleCreated); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Equal(1, provider.LinesPerPage); + + textBoxBase.Multiline = true; + Assert.Equal(lines, provider.LinesPerPage); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_LinesPerPage_ReturnsMinusOne_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Equal(-1, provider.LinesPerPage); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_FirstVisibleLine_Get_ReturnsCorrectValue() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Multiline = true; + textBoxBase.Size = new Size(50, 100); + textBoxBase.CreateControl(); + Assert.True(textBoxBase.IsHandleCreated); + + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Equal(0, provider.FirstVisibleLine); + + provider.LineScroll(0, 2); + Assert.Equal(0, provider.FirstVisibleLine); + + textBoxBase.Text = "Some long long test text for testing GetFirstVisibleLine method"; + provider.LineScroll(0, 2); + Assert.Equal(2, provider.FirstVisibleLine); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_FirstVisibleLine_Get_ReturnsMinuOne_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + int line = provider.FirstVisibleLine; + Assert.Equal(-1, line); + + textBoxBase.Multiline = true; + textBoxBase.Size = new Size(30, 100); + + line = provider.FirstVisibleLine; + Assert.Equal(-1, line); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_LineCount_Get_ReturnsCorrectValue() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.Equal(1, provider.LinesCount); + + textBoxBase.Multiline = true; + textBoxBase.Size = new Size(30, 50); + Assert.Equal(1, provider.LinesCount); + + textBoxBase.Text += "1\r\n"; + Assert.Equal(2, provider.LinesCount); + + textBoxBase.Text += "2\r\n"; + Assert.Equal(3, provider.LinesCount); + + textBoxBase.Text += "3\r\n"; + Assert.Equal(4, provider.LinesCount); + + textBoxBase.Text += "4\r\n"; + Assert.Equal(5, provider.LinesCount); + + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_LineCount_Get_ReturnsMinusOne_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.Equal(-1, provider.LinesCount); + + textBoxBase.Multiline = true; + textBoxBase.Size = new Size(30, 50); + Assert.Equal(-1, provider.LinesCount); + + textBoxBase.Text += "1\r\n"; + Assert.Equal(-1, provider.LinesCount); + + Assert.False(textBoxBase.IsHandleCreated); + } + + public static IEnumerable TextBoxBase_GetLineFromCharIndex_TestData() + { + yield return new object[] { new Size(50, 20), false, 0, 0 }; + yield return new object[] { new Size(50, 20), false, 50, 0 }; + yield return new object[] { new Size(100, 50), true, 50, 3 }; + yield return new object[] { new Size(50, 50), true, 50, 8 }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBase_GetLineFromCharIndex_TestData))] + public void TextBoxBaseUiaTextProvider_GetLineFromCharIndex_ReturnsCorrectValue(Size size, bool multiline, int charIndex, int expectedLine) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Multiline = multiline }; + textBoxBase.CreateControl(); + textBoxBase.Text = "Some test text for testing GetLineFromCharIndex method"; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + int actualLine = provider.GetLineFromCharIndex(charIndex); + Assert.Equal(expectedLine, actualLine); + Assert.True(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameter 'expectedLine' + [WinFormsTheory] + [MemberData(nameof(TextBoxBase_GetLineFromCharIndex_TestData))] + public void TextBoxBaseUiaTextProvider_GetLineFromCharIndex_ReturnsMinusOne_WithoutHandle(Size size, bool multiline, int charIndex, int expectedLine) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Size = size, + Multiline = multiline, + Text = "Some test text for testing GetLineFromCharIndex method" + }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + int actualLine = provider.GetLineFromCharIndex(charIndex); + + Assert.Equal(-1, actualLine); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + public static IEnumerable TextBoxBaseUiaTextProvider_GetLineIndex_TestData() + { + yield return new object[] { new Size(50, 20), false, 0, 0 }; + yield return new object[] { new Size(50, 20), false, 3, 0 }; + yield return new object[] { new Size(50, 50), true, 3, 19 }; + yield return new object[] { new Size(100, 50), true, 3, 40 }; + yield return new object[] { new Size(50, 50), true, 100, -1 }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetLineIndex_TestData))] + public void TextBoxBaseUiaTextProvider_GetLineIndex_ReturnsCorrectValue(Size size, bool multiline, int lineIndex, int expectedIndex) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Size = size, + Multiline = multiline, + Text = "Some test text for testing GetLineIndex method" + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + int actualIndex = provider.GetLineIndex(lineIndex); + + Assert.Equal(expectedIndex, actualIndex); + Assert.True(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameter 'expectedIndex' + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetLineIndex_TestData))] + public void TextBoxBaseUiaTextProvider_GetLineIndex_ReturnsMinusOne_WithoutHandle(Size size, bool multiline, int lineIndex, int expectedIndex) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Size = size, + Multiline = multiline, + Text = "Some test text for testing GetLineIndex method" + }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + int actualIndex = provider.GetLineIndex(lineIndex); + + Assert.Equal(-1, actualIndex); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetLogfont_ReturnsCorrectValue() + { + using (new NoAssertContext()) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + LOGFONTW expected = LOGFONTW.FromFont(textBoxBase.Font); + LOGFONTW actual = provider.Logfont; + Assert.False(string.IsNullOrEmpty(actual.FaceName.ToString())); + Assert.Equal(expected, actual); + Assert.True(textBoxBase.IsHandleCreated); + } + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetLogfont_ReturnsEmpty_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + LOGFONTW expected = new LOGFONTW(); + LOGFONTW actual = provider.Logfont; + Assert.True(string.IsNullOrEmpty(actual.FaceName.ToString())); + Assert.Equal(expected, actual); + Assert.False(textBoxBase.IsHandleCreated); + } + + public static IEnumerable TextBoxBaseUiaTextProvider_GetPositionFromChar_TestData() + { + yield return new object[] { new Size(50, 20), "Some test text for testing", false, 0, new Point(1, 0) }; + yield return new object[] { new Size(50, 20), "Some test text for testing", false, 15, new Point(79, 0) }; + yield return new object[] { new Size(50, 20), "Some test text for testing", true, 15, new Point(27, 30) }; + yield return new object[] { new Size(100, 60), "This is a\r\nlong long text\r\nfor testing\r\nGetPositionFromChar method", true, 0, new Point(4, 1) }; + yield return new object[] { new Size(100, 60), "This is a\r\nlong long text\r\nfor testing\r\nGetPositionFromChar method", true, 6, new Point(31, 1) }; + yield return new object[] { new Size(100, 60), "This is a\r\nlong long text\r\nfor testing\r\nGetPositionFromChar method", true, 26, new Point(78, 16) }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetPositionFromChar_TestData))] + public void TextBoxBaseUiaTextProvider_GetPositionFromChar_ReturnsCorrectValue(Size size, string text, bool multiline, int charIndex, Point expectedPoint) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Text = text, Multiline = multiline }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + 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(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameter 'expectedPoint' + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetPositionFromChar_TestData))] + public void TextBoxBaseUiaTextProvider_GetPositionFromChar_ReturnsEmpty_WithoutHanlde(Size size, string text, bool multiline, int charIndex, Point expectedPoint) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Text = text, Multiline = multiline }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Point actualPoint = provider.GetPositionFromChar(charIndex); + Assert.Equal(Point.Empty, actualPoint); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + public static IEnumerable TextBoxBaseUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_TestData() + { + yield return new object[] { new Size(50, 20), "", false, 0, new Point(0, 0) }; + yield return new object[] { new Size(50, 20), "Some test text", false, 100, new Point(0, 0) }; + yield return new object[] { new Size(50, 20), "Some test text", false, -1, new Point(0, 0) }; + yield return new object[] { new Size(50, 20), "Some test text", false, 12, new Point(71, 0) }; + yield return new object[] { new Size(50, 20), "Some test text", true, 12, new Point(19, 30) }; + yield return new object[] { new Size(100, 60), "Some test \n text", false, 10, new Point(56, 0) }; + yield return new object[] { new Size(100, 60), "Some test \n text", true, 10, new Point(59, 1) }; + yield return new object[] { new Size(100, 60), "Some test \r\n text", false, 10, new Point(56, 0) }; + yield return new object[] { new Size(100, 60), "Some test \r\n text", true, 10, new Point(59, 1) }; + yield return new object[] { new Size(100, 60), "Some test \r\n text", false, 12, new Point(60, 0) }; + yield return new object[] { new Size(100, 60), "Some test \r\n text", true, 12, new Point(7, 16) }; + yield return new object[] { new Size(100, 60), "Some test \t text", false, 10, new Point(57, 0) }; + yield return new object[] { new Size(100, 60), "Some test \t text", true, 10, new Point(60, 1) }; + yield return new object[] { new Size(40, 60), "Some test \t text", true, 12, new Point(8, 46) }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_TestData))] + public void TextBoxBaseUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue(Size size, string text, bool multiline, int charIndex, Point expectedPoint) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Text = text, Multiline = multiline }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Point actualPoint = provider.GetPositionFromCharForUpperRightCorner(charIndex, textBoxBase.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(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameter 'expectedPoint' + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsCorrectValue_TestData))] + public void TextBoxBaseUiaTextProvider_GetPositionFromCharForUpperRightCorner_ReturnsMinusOne_WithoutHandle(Size size, string text, bool multiline, int charIndex, Point expectedPoint) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Text = text, Multiline = multiline }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Point actualPoint = provider.GetPositionFromCharForUpperRightCorner(charIndex, textBoxBase.Text); + Assert.Equal(Point.Empty, actualPoint); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + public static IEnumerable TextBoxBaseUiaTextProvider_GetFormattingRectangle_TestData() + { + yield return new object[] { false, new Size(0, 0), new Rectangle(1, 0, 78, 16) }; + yield return new object[] { false, new Size(50, 50), new Rectangle(1, 1, 44, 15) }; + yield return new object[] { false, new Size(250, 100), new Rectangle(1, 1, 244, 15) }; + yield return new object[] { true, new Size(0, 0), new Rectangle(4, 0, 72, 16) }; + yield return new object[] { true, new Size(50, 50), new Rectangle(4, 1, 38, 30) }; + yield return new object[] { true, new Size(250, 100), new Rectangle(4, 1, 238, 90) }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetFormattingRectangle_TestData))] + public void TextBoxBaseUiaTextProvider_GetFormattingRectangle_ReturnsCorrectValue(bool multiline, Size size, Rectangle expectedRectangle) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Multiline = multiline }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Rectangle providerRectangle = provider.BoundingRectangle; + + Assert.Equal(expectedRectangle, providerRectangle); + Assert.True(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameter 'expectedRectangle' + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetFormattingRectangle_TestData))] + public void TextBoxBaseUiaTextProvider_GetFormattingRectangle_ReturnsEmpty_WithoutHandle(bool multiline, Size size, Rectangle expectedRectangle) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size, Multiline = multiline }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Rectangle providerRectangle = provider.BoundingRectangle; + + Assert.Equal(Drawing.Rectangle.Empty, providerRectangle); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + [WinFormsTheory] + [InlineData("")] + [InlineData("Text")] + [InlineData("Some test text")] + public void TextBoxBaseUiaTextProvider_Text_ReturnsCorrectValue(string text) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Text = text; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + textBoxBase.CreateControl(); + string expected = textBoxBase.Text; + string actual = provider.Text.Trim('\0'); + Assert.Equal(expected, actual); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData("")] + [InlineData("Text")] + [InlineData("Some test text")] + public void TextBoxBaseUiaTextProvider_Text_ReturnsEmpty_WithoutHandle(string text) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Text = text; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + string expected = string.Empty; + string actual = provider.Text.Trim('\0'); + Assert.Equal(expected, actual); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData("")] + [InlineData("Text")] + [InlineData("Some test text for testing")] + [InlineData("Some very very very long test text for testing GetTextLength method")] + public void TextBoxBaseUiaTextProvider_TextLength_ReturnsCorrectValue(string text) + { + using TextBoxBase textBoxBase = new SubTextBoxBase + { + Text = text + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Equal(textBoxBase.Text.Length, provider.TextLength); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData("")] + [InlineData("Text")] + [InlineData("Some test text")] + public void TextBoxBaseUiaTextProvider_TextLength_ReturnsMinusOne_WithoutHandle(string text) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Text = text; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Equal(-1, provider.TextLength); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_WindowExStyle_ReturnsCorrectValue() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + WS_EX actual = provider.WindowExStyle; + Assert.Equal(WS_EX.CLIENTEDGE, actual); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_WindowExStyle_ReturnsLeft_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + WS_EX actual = provider.WindowExStyle; + Assert.Equal(WS_EX.LEFT, actual); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_EditStyle_ReturnsCorrectValue() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + ES actual = provider.EditStyle; + Assert.True(actual.HasFlag(ES.LEFT)); + Assert.True(actual.HasFlag(ES.AUTOVSCROLL)); + Assert.True(actual.HasFlag(ES.AUTOHSCROLL)); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_EditStyle_ReturnsLeft_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + ES actual = provider.EditStyle; + Assert.Equal(ES.LEFT, actual); + Assert.False(textBoxBase.IsHandleCreated); + } + + public static IEnumerable TextBoxBase_GetVisibleRangePoints_ForSinglelineTextBox_TestData() + { + yield return new object[] { new Size(0, 0), 0, 0 }; + yield return new object[] { new Size(0, 20), 0, 0 }; + yield return new object[] { new Size(30, 30), 0, 4 }; + yield return new object[] { new Size(50, 20), 0, 8 }; + yield return new object[] { new Size(150, 20), 0, 26 }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBase_GetVisibleRangePoints_ForSinglelineTextBox_TestData))] + public void TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ForSinglelineTextBox_ReturnsCorrectValue(Size size, int expectedStart, int expectedEnd) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Multiline = false, + Text = "Some test text for testing", + Size = size + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + provider.GetVisibleRangePoints(out int providerVisibleStart, out int providerVisibleEnd); + + Assert.True(providerVisibleStart >= 0); + Assert.True(providerVisibleStart < textBoxBase.Text.Length); + Assert.True(providerVisibleEnd >= 0); + Assert.True(providerVisibleEnd <= textBoxBase.Text.Length); + + Assert.Equal(expectedStart, providerVisibleStart); + Assert.Equal(expectedEnd, providerVisibleEnd); + Assert.True(textBoxBase.IsHandleCreated); + } + + public static IEnumerable TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ForMultilineTextBox_TestData() + { + yield return new object[] { new Size(0, 0), 0, 0 }; + yield return new object[] { new Size(0, 20), 0, 0 }; + yield return new object[] { new Size(30, 30), 0, 3 }; + yield return new object[] { new Size(50, 20), 0, 6 }; + yield return new object[] { new Size(120, 20), 0, 20 }; + yield return new object[] { new Size(50, 80), 0, 26 }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ForMultilineTextBox_TestData))] + public void TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ForMultilineTextBox_ReturnsCorrectValue(Size size, int expectedStart, int expectedEnd) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Multiline = true, + Text = "Some test text for testing", + Size = size, + WordWrap = true + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + provider.GetVisibleRangePoints(out int providerVisibleStart, out int providerVisibleEnd); + + Assert.True(providerVisibleStart >= 0); + Assert.True(providerVisibleStart < textBoxBase.Text.Length); + Assert.True(providerVisibleEnd >= 0); + Assert.True(providerVisibleEnd <= textBoxBase.Text.Length); + + Assert.Equal(expectedStart, providerVisibleStart); + Assert.Equal(expectedEnd, providerVisibleEnd); + Assert.True(textBoxBase.IsHandleCreated); + } + +#pragma warning disable xUnit1026 // Disable xUnit1026 warning: The method doesn't use parameters 'expectedStart' and 'expectedEnd' + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ForMultilineTextBox_TestData))] + public void TextBoxBaseUiaTextProvider_GetVisibleRangePoints_ReturnsZeros_WithoutHandle(Size size, int expectedStart, int expectedEnd) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() { Size = size }; + textBoxBase.Text = "Some test text for testing"; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + provider.GetVisibleRangePoints(out int providerVisibleStart, out int providerVisibleEnd); + + Assert.Equal(0, providerVisibleStart); + Assert.Equal(0, providerVisibleEnd); + Assert.False(textBoxBase.IsHandleCreated); + } +#pragma warning restore xUnit1026 + + public static IEnumerable TextBoxBaseUiaTextProvider_GetVisibleRanges_TestData() + { + yield return new object[] { new Size(0, 0) }; + yield return new object[] { new Size(100, 20) }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetVisibleRanges_TestData))] + public void TextBoxBaseUiaTextProvider_GetVisibleRanges_ReturnsCorrectValue(Size size) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Text = "Some test text for testing", + Size = size + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.NotNull(provider.GetVisibleRanges()); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_GetVisibleRanges_TestData))] + public void TextBoxBaseUiaTextProvider_GetVisibleRanges_ReturnsNull_WithoutHandle(Size size) + { + using SubTextBoxBase textBoxBase = new SubTextBoxBase() + { + Text = "Some test text for testing", + Size = size + }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.Null(provider.GetVisibleRanges()); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_RangeFromAnnotation_DoesntThrowAnException() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + // RangeFromAnnotation doesn't throw an exception + UiaCore.ITextRangeProvider range = provider.RangeFromAnnotation(textBoxBase.AccessibilityObject); + // RangeFromAnnotation implementation can be changed so this test can be changed too + Assert.NotNull(range); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_RangeFromChild_DoesntThrowAnException() + { + using (new NoAssertContext()) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + // RangeFromChild doesn't throw an exception + UiaCore.ITextRangeProvider range = provider.RangeFromChild(textBoxBase.AccessibilityObject); + // RangeFromChild implementation can be changed so this test can be changed too + Assert.Null(range); + } + } + + public static IEnumerable TextBoxBaseUiaTextProvider_RangeFromPoint_TestData() + { + yield return new object[] { Point.Empty }; + yield return new object[] { new Point(10, 10) }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_RangeFromPoint_TestData))] + public void TextBoxBaseUiaTextProvider_RangeFromPoint_DoesntThrowAnException(Point point) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + UiaTextRange textRangeProvider = provider.RangeFromPoint(point) as UiaTextRange; + Assert.NotNull(textRangeProvider); + + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBaseUiaTextProvider_RangeFromPoint_TestData))] + public void TextBoxBaseUiaTextProvider_RangeFromPoint_ReturnsNull_WithoutHandle(Point point) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + UiaTextRange textRangeProvider = provider.RangeFromPoint(point) as UiaTextRange; + Assert.Null(textRangeProvider); + + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(2, 5)] + [InlineData(0, 10)] + public void TextBoxBaseUiaTextProvider_SetSelection_GetSelection_ReturnCorrectValue(int start, int end) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + textBoxBase.Text = "Some test text for testing"; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + provider.SetSelection(start, end); + UiaCore.ITextRangeProvider[] selection = provider.GetSelection(); + Assert.NotNull(selection); + + UiaTextRange textRange = selection[0] as UiaTextRange; + Assert.NotNull(textRange); + + Assert.Equal(start, textRange.Start); + Assert.Equal(end, textRange.End); + + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(2, 5)] + [InlineData(0, 10)] + public void TextBoxBaseUiaTextProvider_SetSelection_GetSelection_DontWork_WithoutHandle(int start, int end) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.Text = "Some test text for testing"; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + provider.SetSelection(start, end); + Assert.False(textBoxBase.IsHandleCreated); + UiaCore.ITextRangeProvider[] selection = provider.GetSelection(); + Assert.Null(selection); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(-5, 10)] + [InlineData(5, 100)] + public void TextBoxBaseUiaTextProvider_SetSelection_DoesntSelectText_IfIncorrectArguments(int start, int end) + { + using (new NoAssertContext()) + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + textBoxBase.CreateControl(); + textBoxBase.Text = "Some test text for testing"; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + 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.True(textBoxBase.IsHandleCreated); + } + } + + [WinFormsTheory] + [InlineData(0)] + [InlineData(2)] + public void TextBoxBaseUiaTextProvider_LineScroll_ReturnCorrectValue(int expectedLine) + { + using TextBoxBase textBoxBase = new SubTextBoxBase + { + Multiline = true, + Text = "Some long long test text for testing GetFirstVisibleLine method", + Size = new Size(50, 100) + }; + textBoxBase.CreateControl(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + + Assert.True(provider.LineScroll(0, expectedLine)); + Assert.Equal(expectedLine, provider.FirstVisibleLine); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsTheory] + [InlineData(0)] + [InlineData(2)] + public void TextBoxBaseUiaTextProvider_LineScroll_DoesntWork_WitoutHandle(int expectedLine) + { + using TextBoxBase textBoxBase = new SubTextBoxBase + { + Multiline = true, + Size = new Size(50, 100) + }; + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + textBoxBase.Text = "Some long long test text for testing GetFirstVisibleLine method"; + + Assert.False(provider.LineScroll(0, expectedLine)); + Assert.Equal(-1, provider.FirstVisibleLine); + Assert.False(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetLogfont_ReturnSegoe_ByDefault() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + Assert.NotEqual(IntPtr.Zero, textBoxBase.Handle); + + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + LOGFONTW logFont = provider.Logfont; + string actual = logFont.FaceName.ToString(); + Assert.Equal("Segoe UI", actual); + Assert.True(textBoxBase.IsHandleCreated); + } + + [WinFormsFact] + public void TextBoxBaseUiaTextProvider_GetLogfont_ReturnEmpty_WithoutHandle() + { + using TextBoxBase textBoxBase = new SubTextBoxBase(); + TextBoxBaseUiaTextProvider provider = new TextBoxBaseUiaTextProvider(textBoxBase); + Assert.False(textBoxBase.IsHandleCreated); + + Assert.Equal(new LOGFONTW(), provider.Logfont); + } + + private class SubTextBoxBase : TextBoxBase + { } + } +} diff --git a/src/System.Windows.Forms/tests/UnitTests/TextBoxBaseTests.cs b/src/System.Windows.Forms/tests/UnitTests/TextBoxBaseTests.cs index 68f039bef8b..d54d70aa0bd 100644 --- a/src/System.Windows.Forms/tests/UnitTests/TextBoxBaseTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/TextBoxBaseTests.cs @@ -1235,8 +1235,7 @@ public void TextBoxBase_Handle_GetMargins_Success(bool multiline, int expected) { Multiline = multiline }; - - Assert.NotEqual(IntPtr.Zero, control.Handle); + control.CreateControl(); IntPtr result = User32.SendMessageW(control.Handle, (User32.WM)User32.EM.GETMARGINS); Assert.Equal(expected, PARAM.LOWORD(result)); Assert.Equal(expected, PARAM.HIWORD(result)); @@ -4873,6 +4872,7 @@ public void TextBoxBase_GetLineFromCharIndex_InvokeNotEmpty_Success(int index) { Text = "text" }; + control.CreateControl(); Assert.Equal(0, control.GetLineFromCharIndex(index)); Assert.True(control.IsHandleCreated); } @@ -4884,7 +4884,7 @@ public void TextBoxBase_GetLineFromCharIndex_InvokeNotEmpty_Success(int index) public void TextBoxBase_GetLineFromCharIndex_InvokeEmptyWithHandle_Success(int index) { using var control = new SubTextBox(); - Assert.NotEqual(IntPtr.Zero, control.Handle); + control.CreateControl(); int invalidatedCallCount = 0; control.Invalidated += (sender, e) => invalidatedCallCount++; int styleChangedCallCount = 0; @@ -4911,7 +4911,7 @@ public void TextBoxBase_GetLineFromCharIndex_InvokeNotEmptyWithHandle_Success(in { Text = "text" }; - Assert.NotEqual(IntPtr.Zero, control.Handle); + control.CreateControl(); int invalidatedCallCount = 0; control.Invalidated += (sender, e) => invalidatedCallCount++; int styleChangedCallCount = 0; @@ -7798,6 +7798,7 @@ public void TextBoxBase_WndProc_InvokeReflectCommandWithHandle_Success(IntPtr wP Assert.Equal(0, styleChangedCallCount); Assert.Equal(0, createdCallCount); } + [WinFormsTheory] [InlineData(true, 3)] [InlineData(false, 0)] @@ -7820,6 +7821,7 @@ public void TextBoxBase_WndProc_InvokeSetFontWithoutHandle_ReturnsExpected(bool Assert.Equal(IntPtr.Zero, m.Result); Assert.Equal(!multiline, control.IsHandleCreated); Assert.Equal(0, textChangedCallCount); + control.CreateControl(); IntPtr result = SendMessageW(control.Handle, (WM)EM.GETMARGINS); Assert.Equal(expectedMargin, PARAM.HIWORD(result)); Assert.Equal(expectedMargin, PARAM.LOWORD(result)); @@ -7977,5 +7979,48 @@ private class SubTextBox : TextBox public new void WndProc(ref Message m) => base.WndProc(ref m); } + + private class SubTextBoxBase : TextBoxBase + { + } + + public static IEnumerable TextBoxBase_GetLineFromCharIndex_TestData() + { + yield return new object[] { new Size(50, 20), false, 0, 0 }; + yield return new object[] { new Size(50, 20), false, 50, 0 }; + yield return new object[] { new Size(100, 50), true, 50, 3 }; + yield return new object[] { new Size(50, 50), true, 50, 8 }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBase_GetLineFromCharIndex_TestData))] + public void TextBoxBase_GetLineFromCharIndex_ReturnsCorrectValue(Size size, bool multiline, int charIndex, int expectedLine) + { + using var textBoxBase = new SubTextBoxBase() { Size = size, Multiline = multiline }; + textBoxBase.Text = "Some test text for testing GetLineFromCharIndex method"; + int actualLine = textBoxBase.GetLineFromCharIndex(charIndex); + Assert.Equal(expectedLine, actualLine); + } + + public static IEnumerable TextBoxBase_GetPositionFromCharIndex_TestData() + { + yield return new object[] { new Size(50, 20), false, 0, new Point(1, 0) }; + yield return new object[] { new Size(50, 20), false, 15, new Point(79, 0) }; + yield return new object[] { new Size(50, 50), true, 12, new Point(14, 31) }; + yield return new object[] { new Size(100, 50), true, 22, new Point(37, 16) }; + yield return new object[] { new Size(50, 50), true, 100, Point.Empty }; + yield return new object[] { new Size(50, 50), true, -1, Point.Empty }; + } + + [WinFormsTheory] + [MemberData(nameof(TextBoxBase_GetPositionFromCharIndex_TestData))] + public void TextBoxBase_GetPositionFromCharIndex_ReturnsCorrectValue(Size size, bool multiline, int charIndex, Point expectedPoint) + { + using var textBoxBase = new SubTextBoxBase() { Size = size, Multiline = multiline }; + textBoxBase.Text = "Some test text for testing GetPositionFromCharIndex method"; + Point actualPoint = textBoxBase.GetPositionFromCharIndex(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); + } } } diff --git a/src/System.Windows.Forms/tests/UnitTests/TextBoxTests.cs b/src/System.Windows.Forms/tests/UnitTests/TextBoxTests.cs index acf889537a1..831df8cbabd 100644 --- a/src/System.Windows.Forms/tests/UnitTests/TextBoxTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/TextBoxTests.cs @@ -455,13 +455,14 @@ public void TextBox_CreateAccessibilityInstance_Invoke_ReturnsExpected(bool crea } Assert.Equal(createControl, control.IsHandleCreated); - Control.ControlAccessibleObject instance = Assert.IsType(control.CreateAccessibilityInstance()); + Control.ControlAccessibleObject instance = Assert.IsType(control.CreateAccessibilityInstance()); Assert.Equal(createControl, control.IsHandleCreated); Assert.NotNull(instance); Assert.Same(control, instance.Owner); Assert.Equal(expectedAccessibleRole, instance.Role); Assert.NotSame(control.CreateAccessibilityInstance(), instance); Assert.NotSame(control.AccessibilityObject, instance); + Assert.Equal(createControl, control.IsHandleCreated); } [WinFormsFact] @@ -471,12 +472,13 @@ public void TextBox_CreateAccessibilityInstance_InvokeWithCustomRole_ReturnsExpe { AccessibleRole = AccessibleRole.HelpBalloon }; - Control.ControlAccessibleObject instance = Assert.IsType(control.CreateAccessibilityInstance()); + Control.ControlAccessibleObject instance = Assert.IsType(control.CreateAccessibilityInstance()); Assert.NotNull(instance); Assert.Same(control, instance.Owner); Assert.Equal(AccessibleRole.HelpBalloon, instance.Role); Assert.NotSame(control.CreateAccessibilityInstance(), instance); Assert.NotSame(control.AccessibilityObject, instance); + Assert.False(control.IsHandleCreated); } [WinFormsFact]