diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Richedit/Interop.SCF.cs b/src/System.Windows.Forms.Primitives/src/Interop/Richedit/Interop.SCF.cs
index 0cc345f14bd..e26f2198b17 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/Richedit/Interop.SCF.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/Richedit/Interop.SCF.cs
@@ -11,15 +11,15 @@ internal static partial class Richedit
[Flags]
public enum SCF : uint
{
- SELECTION = 0x0001,
- WORD = 0x0002,
- DEFAULT = 0x0000,
- ALL = 0x0004,
- USEUIRULES = 0x0008,
- ASSOCIATEFONT = 0x0010,
- NOKBUPDATE = 0x0020,
- ASSOCIATEFONT2 = 0x0040,
- SMARTFONT = 0x0080,
+ DEFAULT = 0x0000,
+ SELECTION = 0x0001,
+ WORD = 0x0002,
+ ALL = 0x0004,
+ USEUIRULES = 0x0008,
+ ASSOCIATEFONT = 0x0010,
+ NOKBUPDATE = 0x0020,
+ ASSOCIATEFONT2 = 0x0040,
+ SMARTFONT = 0x0080,
CHARREPFROMLCID = 0x0100,
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider.cs
new file mode 100644
index 00000000000..b7a5b98f7f8
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider.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 System.Drawing;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ [ComImport]
+ [Guid("3589c92c-63f3-4367-99bb-ada653b77cf2")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface ITextProvider
+ {
+ ITextRangeProvider[]? GetSelection();
+
+ ITextRangeProvider[]? GetVisibleRanges();
+
+ ITextRangeProvider? RangeFromChild(IRawElementProviderSimple childElement);
+
+ ITextRangeProvider? RangeFromPoint(Point screenLocation);
+
+ ITextRangeProvider? DocumentRange { get; }
+
+ SupportedTextSelection SupportedTextSelection { get; }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider2.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider2.cs
new file mode 100644
index 00000000000..d73dbdde0b4
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextProvider2.cs
@@ -0,0 +1,34 @@
+// 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.Drawing;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ [ComImport]
+ [Guid("0dc5e6ed-3e16-4bf1-8f9a-a979878bc195")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface ITextProvider2 : ITextProvider
+ {
+ new ITextRangeProvider[]? GetSelection();
+
+ new ITextRangeProvider[]? GetVisibleRanges();
+
+ new ITextRangeProvider? RangeFromChild(IRawElementProviderSimple childElement);
+
+ new ITextRangeProvider? RangeFromPoint(Point screenLocation);
+
+ new ITextRangeProvider? DocumentRange { get; }
+
+ new SupportedTextSelection SupportedTextSelection { get; }
+
+ ITextRangeProvider? RangeFromAnnotation(IRawElementProviderSimple annotation);
+
+ ITextRangeProvider? GetCaretRange(out BOOL isActive);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextRangeProvider.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextRangeProvider.cs
new file mode 100644
index 00000000000..216c3519e72
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.ITextRangeProvider.cs
@@ -0,0 +1,53 @@
+// 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.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ [ComImport]
+ [Guid("5347ad7b-c355-46f8-aff5-909033582f63")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface ITextRangeProvider
+ {
+ ITextRangeProvider Clone();
+
+ BOOL Compare(ITextRangeProvider range);
+
+ int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint);
+
+ void ExpandToEnclosingUnit(TextUnit unit);
+
+ ITextRangeProvider? FindAttribute(int attribute, object value, BOOL backward);
+
+ ITextRangeProvider? FindText(string text, BOOL backward, BOOL ignoreCase);
+
+ object? GetAttributeValue(int attribute);
+
+ double[] GetBoundingRectangles();
+
+ IRawElementProviderSimple GetEnclosingElement();
+
+ string GetText(int maxLength);
+
+ int Move(TextUnit unit, int count);
+
+ int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count);
+
+ void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint);
+
+ void Select();
+
+ void AddToSelection();
+
+ void RemoveFromSelection();
+
+ void ScrollIntoView(BOOL alignToTop);
+
+ IRawElementProviderSimple[] GetChildren();
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.SupportedTextSelection.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.SupportedTextSelection.cs
new file mode 100644
index 00000000000..e9d9050a308
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.SupportedTextSelection.cs
@@ -0,0 +1,19 @@
+// 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;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ [Flags]
+ public enum SupportedTextSelection
+ {
+ None = 0,
+ Single = 1,
+ Multiple = 2
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextAttributeIdentifier.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextAttributeIdentifier.cs
new file mode 100644
index 00000000000..96bb2d93fa6
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextAttributeIdentifier.cs
@@ -0,0 +1,56 @@
+// 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.
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ public enum TextAttributeIdentifier
+ {
+ AnimationStyleAttributeId = 40000,
+ BackgroundColorAttributeId = 40001,
+ BulletStyleAttributeId = 40002,
+ CapStyleAttributeId = 40003,
+ CultureAttributeId = 40004,
+ FontNameAttributeId = 40005,
+ FontSizeAttributeId = 40006,
+ FontWeightAttributeId = 40007,
+ ForegroundColorAttributeId = 40008,
+ HorizontalTextAlignmentAttributeId = 40009,
+ IndentationFirstLineAttributeId = 40010,
+ IndentationLeadingAttributeId = 40011,
+ IndentationTrailingAttributeId = 40012,
+ IsHiddenAttributeId = 40013,
+ IsItalicAttributeId = 40014,
+ IsReadOnlyAttributeId = 40015,
+ IsSubscriptAttributeId = 40016,
+ IsSuperscriptAttributeId = 40017,
+ MarginBottomAttributeId = 40018,
+ MarginLeadingAttributeId = 40019,
+ MarginTopAttributeId = 40020,
+ MarginTrailingAttributeId = 40021,
+ OutlineStylesAttributeId = 40022,
+ OverlineColorAttributeId = 40023,
+ OverlineStyleAttributeId = 40024,
+ StrikethroughColorAttributeId = 40025,
+ StrikethroughStyleAttributeId = 40026,
+ TabsAttributeId = 40027,
+ TextFlowDirectionsAttributeId = 40028,
+ UnderlineColorAttributeId = 40029,
+ UnderlineStyleAttributeId = 40030,
+ AnnotationTypesAttributeId = 40031,
+ AnnotationObjectsAttributeId = 40032,
+ StyleNameAttributeId = 40033,
+ StyleIdAttributeId = 40034,
+ LinkAttributeId = 40035,
+ IsActiveAttributeId = 40036,
+ SelectionActiveEndAttributeId = 40037,
+ CaretPositionAttributeId = 40038,
+ CaretBidiModeAttributeId = 40039,
+ LineSpacingAttributeId = 40040,
+ BeforeParagraphSpacingAttributeId = 40041,
+ AfterParagraphSpacingAttributeId = 40042
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextPatternRangeEndpoint.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextPatternRangeEndpoint.cs
new file mode 100644
index 00000000000..6e208f4a655
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextPatternRangeEndpoint.cs
@@ -0,0 +1,18 @@
+// 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;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ public enum TextPatternRangeEndpoint
+ {
+ Start = 0,
+ End = 1
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextUnit.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextUnit.cs
new file mode 100644
index 00000000000..a1e96a5915a
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.TextUnit.cs
@@ -0,0 +1,22 @@
+// 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.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ public enum TextUnit
+ {
+ Character = 0,
+ Format = 1,
+ Word = 2,
+ Line = 3,
+ Paragraph = 4,
+ Page = 5,
+ Document = 6
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.UiaCoreTypes.cs b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.UiaCoreTypes.cs
new file mode 100644
index 00000000000..f1976270dcc
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/UiaCore/Interop.UiaCoreTypes.cs
@@ -0,0 +1,26 @@
+// 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.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class UiaCore
+ {
+ private static object? s_notSupportedValue;
+
+ [DllImport(Libraries.UiaCore, ExactSpelling = true)]
+ private static extern int UiaGetReservedNotSupportedValue([MarshalAs(UnmanagedType.IUnknown)] out object notSupportedValue);
+
+ public static object UiaGetReservedNotSupportedValue()
+ {
+ if (s_notSupportedValue == null)
+ {
+ UiaGetReservedNotSupportedValue(out s_notSupportedValue);
+ }
+
+ return s_notSupportedValue;
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.Atom.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.Atom.cs
new file mode 100644
index 00000000000..9e6a0efe06e
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.Atom.cs
@@ -0,0 +1,32 @@
+// 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;
+
+internal static partial class Interop
+{
+ ///
+ /// Simple wrapper for an ATOM
+ ///
+ public struct Atom
+ {
+ // #define MAXINTATOM 0xC000
+ // #define MAKEINTATOM(i) (LPTSTR)((ULONG_PTR)((WORD)(i)))
+ // #define INVALID_ATOM ((ATOM)0)
+
+ // Strange uses for window class atoms
+ // https://blogs.msdn.microsoft.com/oldnewthing/20080501-00/?p=22503/
+
+ public ushort ATOM;
+
+ public Atom(ushort atom) => ATOM = atom;
+
+ public static Atom Null = new Atom(0);
+
+ public bool IsValid => ATOM != 0;
+
+ public static implicit operator uint(Atom atom) => atom.ATOM;
+ public static implicit operator Atom(IntPtr atom) => new Atom((ushort)atom);
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.ES.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.ES.cs
index 8a02c9daae1..cde887982d7 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.ES.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.ES.cs
@@ -11,20 +11,20 @@ internal static partial class User32
[Flags]
public enum ES : uint
{
- LEFT = 0x0000,
- CENTER = 0x0001,
- RIGHT = 0x0002,
- MULTILINE = 0x0004,
- UPPERCASE = 0x0008,
- LOWERCASE = 0x0010,
- PASSWORD = 0x0020,
+ LEFT = 0x0000,
+ CENTER = 0x0001,
+ RIGHT = 0x0002,
+ MULTILINE = 0x0004,
+ UPPERCASE = 0x0008,
+ LOWERCASE = 0x0010,
+ PASSWORD = 0x0020,
AUTOVSCROLL = 0x0040,
AUTOHSCROLL = 0x0080,
- NOHIDESEL = 0x0100,
- OEMCONVERT = 0x0400,
- READONLY = 0x0800,
- WANTRETURN = 0x1000,
- NUMBER = 0x2000
+ NOHIDESEL = 0x0100,
+ OEMCONVERT = 0x0400,
+ READONLY = 0x0800,
+ WANTRETURN = 0x1000,
+ NUMBER = 0x2000
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GCL.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GCL.cs
new file mode 100644
index 00000000000..e95525b8ccc
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GCL.cs
@@ -0,0 +1,22 @@
+// 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.
+
+internal static partial class Interop
+{
+ internal static partial class User32
+ {
+ public enum GCL : int
+ {
+ ///
+ /// (GCL_WNDPROC)
+ ///
+ WNDPROC = -24,
+
+ ///
+ /// (GCW_ATOM)
+ ///
+ ATOM = -32,
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetCaretPos.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetCaretPos.cs
new file mode 100644
index 00000000000..a460fe3acb6
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetCaretPos.cs
@@ -0,0 +1,15 @@
+// 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.Drawing;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class User32
+ {
+ [DllImport(Libraries.User32, ExactSpelling = true)]
+ public static extern BOOL GetCaretPos(out Point pt);
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetClassLong.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetClassLong.cs
new file mode 100644
index 00000000000..a9f2888269d
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetClassLong.cs
@@ -0,0 +1,31 @@
+// 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;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class User32
+ {
+ // We only ever call this on 32 bit so IntPtr is correct
+ // https://msdn.microsoft.com/library/windows/desktop/ms633580.aspx
+ [DllImport(Libraries.User32, SetLastError = true, ExactSpelling = true)]
+ private static extern IntPtr GetClassLongW(IntPtr hWnd, GCL nIndex);
+
+ // https://msdn.microsoft.com/library/windows/desktop/ms633581.aspx
+ [DllImport(Libraries.User32, SetLastError = true, ExactSpelling = true)]
+ private static extern IntPtr GetClassLongPtrW(IntPtr hWnd, GCL nIndex);
+
+ public static IntPtr GetClassLong(IntPtr hWnd, GCL nIndex)
+ {
+ if (IntPtr.Size == 4)
+ {
+ return GetClassLongW(hWnd, nIndex);
+ }
+
+ return GetClassLongPtrW(hWnd, nIndex);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.RegisterClassW.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.RegisterClassW.cs
index d6b4efda8b7..9192ff50b06 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.RegisterClassW.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.RegisterClassW.cs
@@ -2,7 +2,6 @@
// 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;
using System.Runtime.InteropServices;
internal static partial class Interop
@@ -10,6 +9,6 @@ internal static partial class Interop
internal static partial class User32
{
[DllImport(Libraries.User32, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
- public static extern ushort RegisterClassW(ref WNDCLASS lpWndClass);
+ public static extern Atom RegisterClassW(ref WNDCLASS lpWndClass);
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SendMessageW.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SendMessageW.cs
index 99dc582bc92..a1e50d76eb2 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SendMessageW.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SendMessageW.cs
@@ -97,5 +97,19 @@ public unsafe static IntPtr SendMessageW(
return SendMessageW(hWnd, Msg, wParam, (IntPtr)l);
}
}
+
+ public unsafe static IntPtr SendMessageW(
+ IHandle hWnd,
+ WM Msg,
+ ref TWParam wParam,
+ ref TLParam lParam)
+ where TWParam : unmanaged
+ where TLParam : unmanaged
+ {
+ fixed (void* w = &wParam, l = &lParam)
+ {
+ return SendMessageW(hWnd, Msg, (IntPtr)w, (IntPtr)l);
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SetClassLong.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SetClassLong.cs
index 0761eee2d25..4d26404bba9 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SetClassLong.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.SetClassLong.cs
@@ -4,7 +4,6 @@
using System;
using System.Runtime.InteropServices;
-using System.Windows.Forms;
internal static partial class Interop
{
@@ -23,12 +22,8 @@ public static IntPtr SetClassLong(IntPtr hWnd, GCL nIndex, IntPtr dwNewLong)
{
return SetClassLongW(hWnd, nIndex, dwNewLong);
}
- return SetClassLongPtrW(hWnd, nIndex, dwNewLong);
- }
- public enum GCL : int
- {
- WNDPROC = -24
+ return SetClassLongPtrW(hWnd, nIndex, dwNewLong);
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.VK.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.VK.cs
index 33022d4d46b..34c82477a8c 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.VK.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.VK.cs
@@ -27,6 +27,11 @@ public static class VK
public const int SCROLL = 0x0091;
public const int INSERT = 0x002D;
public const int DELETE = 0x002E;
+ public const int LWIN = 0x5B;
+ public const int RWIN = 0x5C;
+ public const int APPS = 0x5D;
+ public const int RCONTROL = 0xA3;
+ public const int RMENU = 0xA5;
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS.cs
index b88adcbbed5..41e71ce8943 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS.cs
@@ -14,25 +14,25 @@ internal static partial class User32
[Flags]
public enum WS : uint
{
- OVERLAPPED = 0x00000000,
- POPUP = 0x80000000,
- CHILD = 0x40000000,
- MINIMIZE = 0x20000000,
- VISIBLE = 0x10000000,
- DISABLED = 0x08000000,
- CLIPSIBLINGS = 0x04000000,
- CLIPCHILDREN = 0x02000000,
- MAXIMIZE = 0x01000000,
- CAPTION = 0x00C00000,
- BORDER = 0x00800000,
- DLGFRAME = 0x00400000,
- VSCROLL = 0x00200000,
- HSCROLL = 0x00100000,
- SYSMENU = 0x00080000,
- THICKFRAME = 0x00040000,
- TABSTOP = 0x00010000,
- MINIMIZEBOX = 0x00020000,
- MAXIMIZEBOX = 0x00010000
+ OVERLAPPED = 0x00000000,
+ POPUP = 0x80000000,
+ CHILD = 0x40000000,
+ MINIMIZE = 0x20000000,
+ VISIBLE = 0x10000000,
+ DISABLED = 0x08000000,
+ CLIPSIBLINGS = 0x04000000,
+ CLIPCHILDREN = 0x02000000,
+ MAXIMIZE = 0x01000000,
+ CAPTION = 0x00C00000,
+ BORDER = 0x00800000,
+ DLGFRAME = 0x00400000,
+ VSCROLL = 0x00200000,
+ HSCROLL = 0x00100000,
+ SYSMENU = 0x00080000,
+ THICKFRAME = 0x00040000,
+ TABSTOP = 0x00010000,
+ MINIMIZEBOX = 0x00020000,
+ MAXIMIZEBOX = 0x00010000
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS_EX.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS_EX.cs
index 70c3b02987f..81a810cb577 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS_EX.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.WS_EX.cs
@@ -14,33 +14,34 @@ internal static partial class User32
[Flags]
public enum WS_EX : uint
{
- DLGMODALFRAME = 0x00000001,
- NOPARENTNOTIFY = 0x00000004,
- TOPMOST = 0x00000008,
- ACCEPTFILES = 0x00000010,
- TRANSPARENT = 0x00000020,
- MDICHILD = 0x00000040,
- TOOLWINDOW = 0x00000080,
- WINDOWEDGE = 0x00000100,
- CLIENTEDGE = 0x00000200,
- CONTEXTHELP = 0x00000400,
- RIGHT = 0x00001000,
- LEFT = 0x00000000,
- RTLREADING = 0x00002000,
- LTRREADING = 0x00000000,
- LEFTSCROLLBAR = 0x00004000,
- RIGHTSCROLLBAR = 0x00000000,
- CONTROLPARENT = 0x00010000,
- STATICEDGE = 0x00020000,
- APPWINDOW = 0x00040000,
- OVERLAPPEDWINDOW = WINDOWEDGE | CLIENTEDGE,
- PALETTEWINDOW = WINDOWEDGE | TOOLWINDOW | TOPMOST,
- LAYERED = 0x00080000,
- NOINHERITLAYOUT = 0x00100000,
+ DEFAULT = 0x00000000,
+ DLGMODALFRAME = 0x00000001,
+ NOPARENTNOTIFY = 0x00000004,
+ TOPMOST = 0x00000008,
+ ACCEPTFILES = 0x00000010,
+ TRANSPARENT = 0x00000020,
+ MDICHILD = 0x00000040,
+ TOOLWINDOW = 0x00000080,
+ WINDOWEDGE = 0x00000100,
+ CLIENTEDGE = 0x00000200,
+ CONTEXTHELP = 0x00000400,
+ RIGHT = 0x00001000,
+ LEFT = 0x00000000,
+ RTLREADING = 0x00002000,
+ LTRREADING = 0x00000000,
+ LEFTSCROLLBAR = 0x00004000,
+ RIGHTSCROLLBAR = 0x00000000,
+ CONTROLPARENT = 0x00010000,
+ STATICEDGE = 0x00020000,
+ APPWINDOW = 0x00040000,
+ OVERLAPPEDWINDOW = WINDOWEDGE | CLIENTEDGE,
+ PALETTEWINDOW = WINDOWEDGE | TOOLWINDOW | TOPMOST,
+ LAYERED = 0x00080000,
+ NOINHERITLAYOUT = 0x00100000,
NOREDIRECTIONBITMAP = 0x00200000,
- LAYOUTRTL = 0x00400000,
- COMPOSITED = 0x02000000,
- NOACTIVATE = 0x08000000
+ LAYOUTRTL = 0x00400000,
+ COMPOSITED = 0x02000000,
+ NOACTIVATE = 0x08000000
}
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/PublicAPI.Unshipped.txt b/src/System.Windows.Forms.Primitives/src/PublicAPI.Unshipped.txt
index e69de29bb2d..0122d2c3883 100644
--- a/src/System.Windows.Forms.Primitives/src/PublicAPI.Unshipped.txt
+++ b/src/System.Windows.Forms.Primitives/src/PublicAPI.Unshipped.txt
@@ -0,0 +1,3 @@
+System.Windows.Forms.Automation.IAutomationLiveRegion
+System.Windows.Forms.Automation.IAutomationLiveRegion.LiveSetting.get -> System.Windows.Forms.Automation.AutomationLiveSetting
+System.Windows.Forms.Automation.IAutomationLiveRegion.LiveSetting.set -> void
\ No newline at end of file
diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/CapStyle.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/CapStyle.cs
new file mode 100644
index 00000000000..0f233c2a01c
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/CapStyle.cs
@@ -0,0 +1,18 @@
+// 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.
+
+namespace System.Windows.Forms.Automation
+{
+ internal enum CapStyle
+ {
+ Other = -1,
+ None = 0,
+ SmallCap = 1,
+ AllCap = 2,
+ AllPetiteCaps = 3,
+ PetiteCaps = 4,
+ Unicase = 5,
+ Titling = 6
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/HorizontalTextAlignment.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/HorizontalTextAlignment.cs
new file mode 100644
index 00000000000..9cf5b8c4b46
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/HorizontalTextAlignment.cs
@@ -0,0 +1,14 @@
+// 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.
+
+namespace System.Windows.Forms.Automation
+{
+ internal enum HorizontalTextAlignment
+ {
+ Left = 0,
+ Centered = 1,
+ Right = 2,
+ Justified = 3
+ }
+}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Automation/IAutomationLiveRegion.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/IAutomationLiveRegion.cs
similarity index 100%
rename from src/System.Windows.Forms/src/System/Windows/Forms/Automation/IAutomationLiveRegion.cs
rename to src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/IAutomationLiveRegion.cs
diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/TextDecorationLineStyle.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/TextDecorationLineStyle.cs
new file mode 100644
index 00000000000..23f94c03b55
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/TextDecorationLineStyle.cs
@@ -0,0 +1,29 @@
+// 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.
+
+namespace System.Windows.Forms.Automation
+{
+ internal enum TextDecorationLineStyle
+ {
+ Other = -1,
+ None = 0,
+ Single = 1,
+ WordsOnly = 2,
+ Double = 3,
+ Dot = 4,
+ Dash = 5,
+ DashDot = 6,
+ DashDotDot = 7,
+ Wavy = 8,
+ ThickSingle = 9,
+ DoubleWavy = 11,
+ ThickWavy = 12,
+ LongDash = 13,
+ ThickDash = 14,
+ ThickDashDot = 15,
+ ThickDashDotDot = 16,
+ ThickDot = 17,
+ ThickLongDash = 18
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs
new file mode 100644
index 00000000000..8e8bb6f224d
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider.cs
@@ -0,0 +1,165 @@
+// 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.Runtime.InteropServices;
+using static Interop.Gdi32;
+using static Interop.UiaCore;
+using static Interop.User32;
+
+namespace System.Windows.Forms.Automation
+{
+ internal abstract class UiaTextProvider : ITextProvider
+ {
+ ///
+ /// The value of a width of an end of a text line as 2 px to a ScreenReader can show it.
+ ///
+ public const int EndOfLineWidth = 2;
+
+ public abstract ITextRangeProvider[]? GetSelection();
+
+ public abstract ITextRangeProvider[]? GetVisibleRanges();
+
+ public abstract ITextRangeProvider? RangeFromChild(IRawElementProviderSimple childElement);
+
+ public abstract ITextRangeProvider? RangeFromPoint(Point screenLocation);
+
+ public abstract ITextRangeProvider? DocumentRange { get; }
+
+ public abstract SupportedTextSelection SupportedTextSelection { get; }
+
+ public abstract Rectangle BoundingRectangle { get; }
+
+ public abstract ES EditStyle { get; }
+
+ public abstract int FirstVisibleLine { get; }
+
+ public abstract bool IsMultiline { get; }
+
+ public abstract bool IsReadingRTL { get; }
+
+ public abstract bool IsReadOnly { get; }
+
+ public abstract bool IsScrollable { get; }
+
+ public abstract int LinesPerPage { get; }
+
+ public abstract int LinesCount { get; }
+
+ public abstract LOGFONTW Logfont { get; }
+
+ public abstract string Text { get; }
+
+ public abstract int TextLength { get; }
+
+ public abstract WS_EX WindowExStyle { get; }
+
+ public abstract WS WindowStyle { get; }
+
+ public abstract int GetLineFromCharIndex(int charIndex);
+
+ public abstract int GetLineIndex(int line);
+
+ public abstract Point GetPositionFromChar(int charIndex);
+
+ public abstract Point GetPositionFromCharForUpperRightCorner(int startCharIndex, string text);
+
+ public abstract void GetVisibleRangePoints(out int visibleStart, out int visibleEnd);
+
+ public abstract bool LineScroll(int charactersHorizontal, int linesVertical);
+
+ public abstract Point PointToScreen(Point pt);
+
+ public abstract void SetSelection(int start, int end);
+
+ public ES GetEditStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (ES)GetWindowLong(new HandleRef(null, hWnd), GWL.STYLE) : ES.LEFT;
+
+ public WS_EX GetWindowExStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (WS_EX)GetWindowLong(new HandleRef(null, hWnd), GWL.EXSTYLE) : WS_EX.LEFT;
+
+ public WS GetWindowStyle(IntPtr hWnd) => hWnd != IntPtr.Zero ? (WS)GetWindowLong(new HandleRef(this, hWnd), GWL.STYLE) : WS.DISABLED;
+
+ public double[] RectListToDoubleArray(List rectArray)
+ {
+ if (rectArray == null || rectArray.Count == 0)
+ {
+ return Array.Empty();
+ }
+
+ double[] doubles = new double[rectArray.Count * 4];
+ int scan = 0;
+
+ for (int i = 0; i < rectArray.Count; i++)
+ {
+ doubles[scan++] = rectArray[i].X;
+ doubles[scan++] = rectArray[i].Y;
+ doubles[scan++] = rectArray[i].Width;
+ doubles[scan++] = rectArray[i].Height;
+ }
+
+ return doubles;
+ }
+
+ public int SendInput(int inputs, ref INPUT input, int size)
+ {
+ Span currentInput = stackalloc INPUT[1];
+ currentInput[0] = input;
+
+ return (int)Interop.User32.SendInput((uint)inputs, currentInput, size);
+ }
+
+ public unsafe int SendKeyboardInputVK(short vk, bool press)
+ {
+ INPUT keyboardInput = new INPUT();
+
+ keyboardInput.type = INPUTENUM.KEYBOARD;
+ keyboardInput.inputUnion.ki.wVk = (ushort)vk;
+ keyboardInput.inputUnion.ki.wScan = 0;
+ keyboardInput.inputUnion.ki.dwFlags = press ? 0 : KEYEVENTF.KEYUP;
+
+ if (IsExtendedKey(vk))
+ {
+ keyboardInput.inputUnion.ki.dwFlags |= KEYEVENTF.EXTENDEDKEY;
+ }
+
+ keyboardInput.inputUnion.ki.time = 0;
+ keyboardInput.inputUnion.ki.dwExtraInfo = IntPtr.Zero;
+
+ return SendInput(1, ref keyboardInput, sizeof(INPUT));
+ }
+
+ private static bool IsExtendedKey(short vk)
+ {
+ // From the SDK:
+ // The extended-key flag indicates whether the keystroke message originated from one of
+ // the additional keys on the enhanced keyboard. The extended keys consist of the ALT and
+ // CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP,
+ // PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK
+ // key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in
+ // the numeric keypad. The extended-key flag is set if the key is an extended key.
+ //
+ // - docs appear to be incorrect. Use of Spy++ indicates that break is not an extended key.
+ // Also, menu key and windows keys also appear to be extended.
+ return vk == unchecked((short)VK.RMENU) ||
+ vk == unchecked((short)VK.RCONTROL) ||
+ vk == unchecked((short)VK.NUMLOCK) ||
+ vk == unchecked((short)VK.INSERT) ||
+ vk == unchecked((short)VK.DELETE) ||
+ vk == unchecked((short)VK.HOME) ||
+ vk == unchecked((short)VK.END) ||
+ vk == unchecked((short)VK.PRIOR) ||
+ vk == unchecked((short)VK.NEXT) ||
+ vk == unchecked((short)VK.UP) ||
+ vk == unchecked((short)VK.DOWN) ||
+ vk == unchecked((short)VK.LEFT) ||
+ vk == unchecked((short)VK.RIGHT) ||
+ vk == unchecked((short)VK.APPS) ||
+ vk == unchecked((short)VK.RWIN) ||
+ vk == unchecked((short)VK.LWIN);
+ // Note that there are no distinct values for the following keys:
+ // numpad divide
+ // numpad enter
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider2.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider2.cs
new file mode 100644
index 00000000000..7deb6b8a26c
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextProvider2.cs
@@ -0,0 +1,15 @@
+// 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.Automation
+{
+ internal abstract class UiaTextProvider2 : UiaTextProvider, UiaCore.ITextProvider2
+ {
+ public abstract UiaCore.ITextRangeProvider? GetCaretRange(out BOOL isActive);
+
+ public abstract UiaCore.ITextRangeProvider? RangeFromAnnotation(UiaCore.IRawElementProviderSimple annotationElement);
+ }
+}
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
new file mode 100644
index 00000000000..478cf0cda5c
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Automation/UiaTextRange.cs
@@ -0,0 +1,958 @@
+// 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.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using static Interop;
+using static Interop.UiaCore;
+using static Interop.User32;
+
+namespace System.Windows.Forms.Automation
+{
+ internal class UiaTextRange : ITextRangeProvider
+ {
+ // Edit controls always use "\r\n" as the line separator, not "\n".
+ // This string is a non-localizable string.
+ private const string LineSeparator = "\r\n";
+
+ private readonly IRawElementProviderSimple _enclosingElement;
+ private readonly UiaTextProvider _provider;
+
+ private int _start;
+ private int _end;
+
+ ///
+ /// A caret position before the first character from a text range, not an index of an item.
+ ///
+ ///
+ /// A caret position after the last character from a text range, not an index of an item.
+ ///
+ ///
+ /// If there is a range "Test string", then start = 0, end = 11.
+ /// If start = 2 and end = 9, the range is "et stri".
+ /// If start=end, that points a caret position only, there is no any text range.
+ ///
+ public UiaTextRange(IRawElementProviderSimple enclosingElement, UiaTextProvider provider, int start, int end)
+ {
+ _enclosingElement = enclosingElement ?? throw new ArgumentNullException(nameof(enclosingElement));
+ _provider = provider ?? throw new ArgumentNullException(nameof(provider));
+
+ if (start > 0)
+ {
+ _start = start;
+ _end = start;
+ }
+
+ if (end > _start)
+ {
+ _end = end;
+ }
+ }
+
+ ///
+ /// Last caret position of this text range.
+ ///
+ internal int End
+ {
+ get => _end;
+ set
+ {
+ // Ensure that we never accidentally get a negative index.
+ if (value < 0)
+ {
+ _end = 0;
+ }
+ else
+ {
+ _end = value;
+ }
+
+ // Ensure that end never moves before start.
+ if (_end < _start)
+ {
+ _start = _end;
+ }
+ }
+ }
+
+ internal int Length
+ {
+ get
+ {
+ if (Start < 0 || End < 0 || Start > End)
+ {
+ return 0;
+ }
+
+ // The subtraction of caret positions returns the length of the text.
+ return End - Start;
+ }
+ }
+
+ ///
+ /// First caret position of this text range.
+ ///
+ internal int Start
+ {
+ get => _start;
+ set
+ {
+ // Ensure that start never moves after end.
+ if (value > _end)
+ {
+ _end = value;
+ }
+
+ // Ensure that we never accidentally get a negative index.
+ if (value < 0)
+ {
+ _start = 0;
+ }
+ else
+ {
+ _start = value;
+ }
+ }
+ }
+
+ ///
+ /// Strictly only needs to be == since never should _start > _end.
+ ///
+ private bool IsDegenerate => _start == _end;
+
+ ITextRangeProvider ITextRangeProvider.Clone() => new UiaTextRange(_enclosingElement, _provider, Start, End);
+
+ ///
+ /// Ranges come from the same element. Only need to compare endpoints.
+ ///
+ BOOL ITextRangeProvider.Compare(ITextRangeProvider range)
+ => (range is UiaTextRange editRange && editRange.Start == Start && editRange.End == End).ToBOOL();
+
+ int ITextRangeProvider.CompareEndpoints(
+ TextPatternRangeEndpoint endpoint,
+ ITextRangeProvider targetRange,
+ TextPatternRangeEndpoint targetEndpoint)
+ {
+ if (!(targetRange is UiaTextRange editRange))
+ {
+ return -1;
+ }
+
+ int e1 = (endpoint == (int)TextPatternRangeEndpoint.Start) ? Start : End;
+ int e2 = (targetEndpoint == (int)TextPatternRangeEndpoint.Start) ? editRange.Start : editRange.End;
+
+ return e1 - e2;
+ }
+
+ void ITextRangeProvider.ExpandToEnclosingUnit(TextUnit unit)
+ {
+ switch (unit)
+ {
+ case TextUnit.Character:
+ // Leave it as it is except the case with 0-range.
+ if (IsDegenerate)
+ {
+ End = MoveEndpointForward(End, TextUnit.Character, 1, out int moved);
+ }
+ break;
+
+ case TextUnit.Word:
+ {
+ // Get the word boundaries.
+ string text = _provider.Text;
+ ValidateEndpoints();
+
+ // Move start left until we reach a word boundary.
+ while (!AtWordBoundary(text, Start))
+ {
+ Start--;
+ }
+
+ // Move end right until we reach word boundary (different from Start).
+ End = Math.Min(Math.Max(End, Start + 1), text.Length);
+
+ while (!AtWordBoundary(text, End))
+ {
+ End++;
+ }
+ }
+ break;
+
+ case TextUnit.Line:
+ {
+ if (_provider.LinesCount != 1)
+ {
+ int startLine = _provider.GetLineFromCharIndex(Start);
+ int startIndex = _provider.GetLineIndex(startLine);
+
+ int endLine = _provider.GetLineFromCharIndex(End);
+ int endIndex;
+ if (endLine < _provider.LinesCount - 1)
+ {
+ endLine++;
+ endIndex = _provider.GetLineIndex(endLine);
+ }
+ else
+ {
+ endIndex = _provider.TextLength;
+ }
+
+ MoveTo(startIndex, endIndex);
+ }
+ else
+ {
+ MoveTo(0, _provider.TextLength);
+ }
+ }
+ break;
+
+ case TextUnit.Paragraph:
+ {
+ // Get the paragraph boundaries.
+ string text = _provider.Text;
+ ValidateEndpoints();
+
+ // Move start left until we reach a paragraph boundary.
+ while (!AtParagraphBoundary(text, Start))
+ {
+ Start--;
+ }
+
+ // Move end right until we reach a paragraph boundary (different from Start).
+ End = Math.Min(Math.Max(End, Start + 1), text.Length);
+
+ while (!AtParagraphBoundary(text, End))
+ {
+ End++;
+ }
+ }
+ break;
+
+ case TextUnit.Format:
+ case TextUnit.Page:
+ case TextUnit.Document:
+ MoveTo(0, _provider.TextLength);
+ break;
+
+ default:
+ throw new InvalidEnumArgumentException(nameof(unit), (int)unit, typeof(TextUnit));
+ }
+ }
+
+ ITextRangeProvider? ITextRangeProvider.FindAttribute(int attributeId, object val, BOOL backwards) => null;
+
+ ITextRangeProvider? ITextRangeProvider.FindText(string text, BOOL backwards, BOOL ignoreCase)
+ {
+ if (text is null)
+ {
+ Debug.Fail("Invalid text range argument. 'text' should not be null.");
+ return null;
+ }
+
+ if (text.Length == 0)
+ {
+ Debug.Fail("Invalid text range argument. 'text' length should be more than 0.");
+ return null;
+ }
+
+ ValidateEndpoints();
+ ReadOnlySpan rangeText = new ReadOnlySpan(_provider.Text.ToCharArray(), Start, Length);
+ StringComparison comparisonType = ignoreCase.IsTrue() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
+
+ // Do a case-sensitive search for the text inside the range.
+ int index = backwards.IsTrue() ? rangeText.LastIndexOf(text, comparisonType) : rangeText.IndexOf(text, comparisonType);
+
+ // If the text was found then create a new range covering the found text.
+ return index >= 0 ? new UiaTextRange(_enclosingElement, _provider, Start + index, Start + index + text.Length) : null;
+ }
+
+ object? ITextRangeProvider.GetAttributeValue(int attributeId) => GetAttributeValue((TextAttributeIdentifier)attributeId);
+
+ double[] ITextRangeProvider.GetBoundingRectangles()
+ {
+ // if this is an end of line
+ if (Start == _provider.TextLength)
+ {
+ Point endlinePoint;
+ User32.GetCaretPos(out endlinePoint);
+ endlinePoint = _provider.PointToScreen(endlinePoint);
+ Rectangle endlineRectangle = new Rectangle(endlinePoint.X, endlinePoint.Y + 2, UiaTextProvider.EndOfLineWidth, Math.Abs(_provider.Logfont.lfHeight) + 1);
+ return new double[] { endlineRectangle.X, endlineRectangle.Y, endlineRectangle.Width, endlineRectangle.Height };
+ }
+
+ // Return zero rectangles for a degenerate-range. We don't return an empty,
+ // but properly positioned, rectangle for degenerate ranges.
+ if (IsDegenerate)
+ {
+ return Array.Empty();
+ }
+
+ 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);
+
+ // 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);
+
+ // Add 2 to Y to get a correct size of a rectangle around a range
+ Rectangle rectangle = new Rectangle(startPoint.X, startPoint.Y + 2, endPoint.X - startPoint.X, clippingRectangle.Height);
+ rectangle.Intersect(clippingRectangle);
+
+ if (rectangle.Width > 0 && rectangle.Height > 0)
+ {
+ rectangle.Offset(mapClientToScreen.X, mapClientToScreen.Y);
+ rectangles.Add(rectangle);
+ }
+
+ return _provider.RectListToDoubleArray(rectangles);
+ }
+
+ IRawElementProviderSimple ITextRangeProvider.GetEnclosingElement() => _enclosingElement;
+
+ string ITextRangeProvider.GetText(int maxLength)
+ {
+ if (maxLength == -1)
+ {
+ maxLength = End + 1;
+ }
+
+ string text = _provider.Text;
+ ValidateEndpoints();
+ maxLength = maxLength >= 0 ? Math.Min(Length, maxLength) : Length;
+
+ return text.Length < maxLength - Start
+ ? text.Substring(Start)
+ : text.Substring(Start, maxLength);
+ }
+
+ int ITextRangeProvider.Move(TextUnit unit, int count)
+ {
+ // Positive count means move forward. Negative count means move backwards.
+ int moved;
+
+ if (count > 0)
+ {
+ // If the range is non-degenerate then we need to collapse the range.
+ // (See the discussion of Count for ITextRange::Move)
+ if (!IsDegenerate)
+ {
+ // If the count is greater than zero, collapse the range at its end point.
+ Start = End;
+ }
+
+ // Move the degenerate range forward by the number of units.
+ int start = Start;
+ Start = MoveEndpointForward(Start, unit, count, out moved);
+
+ // If the start did not change then no move was done.
+ if (start != Start)
+ {
+ return moved;
+ }
+ }
+
+ if (count < 0)
+ {
+ // If the range is non-degenerate then we need to collapse the range.
+ if (!IsDegenerate)
+ {
+ // If the count is less than zero, collapse the range at the starting point.
+ End = Start;
+ }
+
+ // Move the degenerate range backward by the number of units.
+ int end = End;
+ End = MoveEndpointBackward(End, unit, count, out moved);
+
+ // If the end did not change then no move was done.
+ if (end != End)
+ {
+ return moved;
+ }
+ }
+
+ // Moving zero of any unit has no effect.
+ return 0;
+ }
+
+ int ITextRangeProvider.MoveEndpointByUnit(
+ TextPatternRangeEndpoint endpoint,
+ TextUnit unit, int count)
+ {
+ // Positive count means move forward. Negative count means move backwards.
+ bool moveStart = endpoint == TextPatternRangeEndpoint.Start;
+ int moved;
+ int start = Start;
+ int end = End;
+
+ if (count > 0)
+ {
+ if (moveStart)
+ {
+ Start = MoveEndpointForward(Start, unit, count, out moved);
+
+ // If the start did not change then no move was done.
+ return start == Start ? 0 : moved;
+ }
+
+ End = MoveEndpointForward(End, unit, count, out moved);
+
+ // If the end did not change then no move was done.
+ return end == End ? 0 : moved;
+ }
+
+ if (count < 0)
+ {
+ if (moveStart)
+ {
+ Start = MoveEndpointBackward(Start, unit, count, out moved);
+
+ // If the start did not change then no move was done.
+ return start == Start ? 0 : moved;
+ }
+
+ End = MoveEndpointBackward(End, unit, count, out moved);
+
+ // If the end did not change then no move was done.
+ return end == End ? 0 : moved;
+ }
+
+ // Moving zero of any unit has no effect.
+ return 0;
+ }
+
+ void ITextRangeProvider.MoveEndpointByRange(
+ TextPatternRangeEndpoint endpoint,
+ ITextRangeProvider targetRange,
+ TextPatternRangeEndpoint targetEndpoint)
+ {
+ if (!(targetRange is UiaTextRange textRange))
+ {
+ return;
+ }
+
+ int e = (targetEndpoint == TextPatternRangeEndpoint.Start)
+ ? textRange.Start
+ : textRange.End;
+
+ if (endpoint == TextPatternRangeEndpoint.Start)
+ {
+ Start = e;
+ }
+ else
+ {
+ End = e;
+ }
+ }
+
+ void ITextRangeProvider.Select() => _provider.SetSelection(Start, End);
+
+ ///
+ /// Do nothing. Do not throw exception.
+ ///
+ void ITextRangeProvider.AddToSelection()
+ { }
+
+ ///
+ /// Do nothing. Do not throw exception.
+ ///
+ void ITextRangeProvider.RemoveFromSelection()
+ { }
+
+ void ITextRangeProvider.ScrollIntoView(BOOL alignToTop)
+ {
+ if (_provider.IsMultiline)
+ {
+ int newFirstLine = alignToTop.IsTrue()
+ ? _provider.GetLineFromCharIndex(Start)
+ : Math.Max(0, _provider.GetLineFromCharIndex(End) - _provider.LinesPerPage + 1);
+
+ _provider.LineScroll(Start, newFirstLine - _provider.FirstVisibleLine);
+
+ return;
+ }
+
+ if (_provider.IsScrollable)
+ {
+ _provider.GetVisibleRangePoints(out int visibleStart, out int visibleEnd);
+ short key = (short)(Start > visibleStart ? VK.RIGHT : VK.LEFT);
+
+ if (_provider.IsReadingRTL)
+ {
+ if (Start > visibleStart || Start < visibleEnd)
+ {
+ _provider.SendKeyboardInputVK(key, true);
+ _provider.GetVisibleRangePoints(out visibleStart, out visibleEnd);
+ }
+
+ return;
+ }
+
+ if (Start < visibleStart || Start > visibleEnd)
+ {
+ _provider.SendKeyboardInputVK(key, true);
+ _provider.GetVisibleRangePoints(out visibleStart, out visibleEnd);
+ }
+ }
+ }
+
+ ///
+ /// We don't have any children so return an empty array
+ ///
+ IRawElementProviderSimple[] ITextRangeProvider.GetChildren() => Array.Empty();
+
+ ///
+ /// Returns true if index identifies a paragraph boundary within text.
+ ///
+ private static bool AtParagraphBoundary(string text, int index)
+ => string.IsNullOrWhiteSpace(text) || index <= 0 || index >= text.Length || ((text[index - 1] == '\n') && (text[index] != '\n'));
+
+ private static bool AtWordBoundary(string text, int index)
+ {
+ // Returns true if index identifies a word boundary within text.
+ // Following richedit & word precedent the boundaries are at the leading edge of the word
+ // so the span of a word includes trailing whitespace.
+
+ // We are at a word boundary if we are at the beginning or end of the text.
+ if (string.IsNullOrWhiteSpace(text) || index <= 0 || index >= text.Length || AtParagraphBoundary(text, index))
+ {
+ return true;
+ }
+
+ char ch1 = text[index - 1];
+ char ch2 = text[index];
+
+ // An apostrophe does *not* break a word if it follows or precedes characters.
+ if ((char.IsLetterOrDigit(ch1) && IsApostrophe(ch2))
+ || (IsApostrophe(ch1) && char.IsLetterOrDigit(ch2) && index >= 2 && char.IsLetterOrDigit(text[index - 2])))
+ {
+ return false;
+ }
+
+ // The following transitions mark boundaries.
+ // Note: these are constructed to include trailing whitespace.
+ return (char.IsWhiteSpace(ch1) && !char.IsWhiteSpace(ch2))
+ || (char.IsLetterOrDigit(ch1) && !char.IsLetterOrDigit(ch2))
+ || (!char.IsLetterOrDigit(ch1) && char.IsLetterOrDigit(ch2))
+ || (char.IsPunctuation(ch1) && char.IsWhiteSpace(ch2));
+ }
+
+ private static bool IsApostrophe(char ch) => ch == '\'' || ch == (char)0x2019; // Unicode Right Single Quote Mark
+
+ private object? GetAttributeValue(TextAttributeIdentifier textAttributeIdentifier)
+ {
+ return textAttributeIdentifier switch
+ {
+ TextAttributeIdentifier.BackgroundColorAttributeId => GetBackgroundColor(),
+ TextAttributeIdentifier.CapStyleAttributeId => GetCapStyle(_provider.EditStyle),
+ TextAttributeIdentifier.FontNameAttributeId => GetFontName(_provider.Logfont),
+ TextAttributeIdentifier.FontSizeAttributeId => GetFontSize(_provider.Logfont),
+ TextAttributeIdentifier.FontWeightAttributeId => GetFontWeight(_provider.Logfont),
+ TextAttributeIdentifier.ForegroundColorAttributeId => GetForegroundColor(),
+ TextAttributeIdentifier.HorizontalTextAlignmentAttributeId => GetHorizontalTextAlignment(_provider.EditStyle),
+ TextAttributeIdentifier.IsItalicAttributeId => GetItalic(_provider.Logfont),
+ TextAttributeIdentifier.IsReadOnlyAttributeId => GetReadOnly(),
+ TextAttributeIdentifier.StrikethroughStyleAttributeId => GetStrikethroughStyle(_provider.Logfont),
+ TextAttributeIdentifier.UnderlineStyleAttributeId => GetUnderlineStyle(_provider.Logfont),
+ _ => UiaGetReservedNotSupportedValue()
+ };
+ }
+
+ ///
+ /// Helper function to accumulate a list of bounding rectangles for a potentially mult-line range.
+ ///
+ private List GetMultilineBoundingRectangles(string text, Point mapClientToScreen, Rectangle clippingRectangle)
+ {
+ // Remember the line height.
+ int height = Math.Abs(_provider.Logfont.lfHeight);
+
+ // Get the starting and ending lines for the range.
+ int start = Start;
+ int end = End;
+
+ int startLine = _provider.GetLineFromCharIndex(start);
+ int endLine = _provider.GetLineFromCharIndex(end - 1);
+
+ // Adjust the start based on the first visible line.
+ int firstVisibleLine = _provider.FirstVisibleLine;
+ if (firstVisibleLine > startLine)
+ {
+ startLine = firstVisibleLine;
+ start = _provider.GetLineIndex(startLine);
+ }
+
+ // Adjust the end based on the last visible line.
+ int lastVisibleLine = firstVisibleLine + _provider.LinesPerPage - 1;
+ if (lastVisibleLine < endLine)
+ {
+ endLine = lastVisibleLine;
+ end = _provider.GetLineIndex(endLine) - 1;
+ }
+
+ // Adding a rectangle for each line.
+ List rects = new List();
+ int nextLineIndex = _provider.GetLineIndex(startLine);
+
+ for (int i = startLine; i <= endLine; i++)
+ {
+ // Determine the starting coordinate on this line.
+ Point startPoint = _provider.GetPositionFromChar(i == startLine ? start : nextLineIndex);
+
+ // Determine the ending coordinate on this line.
+ Point endPoint;
+
+ if (i == endLine)
+ {
+ endPoint = _provider.GetPositionFromCharForUpperRightCorner(end - 1, text);
+ }
+ else
+ {
+ nextLineIndex = _provider.GetLineIndex(i + 1);
+ endPoint = _provider.GetPositionFromChar(nextLineIndex - 1);
+ }
+
+ // Add a bounding rectangle for this line if it is nonempty.
+ // Add 2 to Y and 1 to Height to get a correct size of a rectangle around a range
+ Rectangle rect = new Rectangle(startPoint.X, startPoint.Y + 2, endPoint.X - startPoint.X, height + 1);
+ rect.Intersect(clippingRectangle);
+ if (rect.Width > 0 && rect.Height > 0)
+ {
+ rect.Offset(mapClientToScreen.X, mapClientToScreen.Y);
+ rects.Add(rect);
+ }
+ }
+
+ return rects;
+ }
+
+ private HorizontalTextAlignment GetHorizontalTextAlignment(ES editStyle)
+ {
+ if (editStyle.HasFlag(ES.CENTER))
+ {
+ return HorizontalTextAlignment.Centered;
+ }
+
+ if (editStyle.HasFlag(ES.RIGHT))
+ {
+ return HorizontalTextAlignment.Right;
+ }
+
+ return HorizontalTextAlignment.Left;
+ }
+
+ private CapStyle GetCapStyle(ES editStyle) => editStyle.HasFlag(ES.UPPERCASE) ? CapStyle.AllCap : CapStyle.None;
+
+ private bool GetReadOnly() => _provider.IsReadOnly;
+
+ private static COLORREF GetBackgroundColor() => GetSysColor(COLOR.WINDOW);
+
+ private static string GetFontName(LOGFONTW logfont) => logfont.FaceName.ToString();
+
+ private double GetFontSize(LOGFONTW logfont)
+ {
+ // Note: this assumes integral point sizes. violating this assumption would confuse the user
+ // because they set something to 7 point but reports that it is, say 7.2 point, due to the rounding.
+ using var dc = User32.GetDcScope.ScreenDC;
+ int lpy = Gdi32.GetDeviceCaps(dc, Gdi32.DeviceCapability.LOGPIXELSY);
+ return Math.Round((double)(-logfont.lfHeight) * 72 / lpy);
+ }
+
+ private static Gdi32.FW GetFontWeight(LOGFONTW logfont) => logfont.lfWeight;
+
+ private static COLORREF GetForegroundColor() => GetSysColor(COLOR.WINDOWTEXT);
+
+ private static bool GetItalic(LOGFONTW logfont) => logfont.lfItalic != 0;
+
+ private static TextDecorationLineStyle GetStrikethroughStyle(LOGFONTW logfont)
+ => logfont.lfStrikeOut != 0 ? TextDecorationLineStyle.Single : TextDecorationLineStyle.None;
+
+ private static TextDecorationLineStyle GetUnderlineStyle(LOGFONTW logfont)
+ => logfont.lfUnderline != 0 ? TextDecorationLineStyle.Single : TextDecorationLineStyle.None;
+
+ ///
+ /// Moves an endpoint forward a certain number of units.
+ ///
+ private int MoveEndpointForward(int index, TextUnit unit, int count, out int moved)
+ {
+ switch (unit)
+ {
+ case TextUnit.Character:
+ {
+ int limit = _provider.TextLength;
+ ValidateEndpoints();
+
+ moved = Math.Min(count, limit - index);
+ index = index + moved;
+
+ index = index > limit ? limit : index;
+ }
+ break;
+
+ case TextUnit.Word:
+ {
+ string text = _provider.Text;
+ ValidateEndpoints();
+ moved = 0;
+
+ while (moved < count && index < text.Length)
+ {
+ index++;
+
+ while (!AtWordBoundary(text, index))
+ {
+ index++;
+ }
+
+ moved++;
+ }
+ }
+ break;
+
+ case TextUnit.Line:
+ {
+ // Figure out what line we are on. If we are in the middle of a line and
+ // are moving left then we'll round up to the next line so that we move
+ // to the beginning of the current line.
+ int line = _provider.GetLineFromCharIndex(index);
+
+ // Limit the number of lines moved to the number of lines available to move
+ // Note lineMax is always >= 1.
+ int lineMax = _provider.LinesCount;
+ moved = Math.Min(count, lineMax - line - 1);
+
+ if (moved > 0)
+ {
+ // move the endpoint to the beginning of the destination line.
+ index = _provider.GetLineIndex(line + moved);
+ }
+ else if (moved == 0 && lineMax == 1)
+ {
+ // There is only one line so get the text length as endpoint.
+ index = _provider.TextLength;
+ moved = 1;
+ }
+ }
+ break;
+
+ case TextUnit.Paragraph:
+ {
+ // Just like moving words but we look for paragraph boundaries instead of
+ // word boundaries.
+ string text = _provider.Text;
+ ValidateEndpoints();
+ moved = 0;
+
+ while (moved < count && index < text.Length)
+ {
+ index++;
+
+ while (!AtParagraphBoundary(text, index))
+ {
+ index++;
+ }
+
+ moved++;
+ }
+ }
+ break;
+
+ case TextUnit.Format:
+ case TextUnit.Page:
+ case TextUnit.Document:
+ {
+ // Since edit controls are plain text moving one uniform format unit will
+ // take us all the way to the end of the document, just like
+ // "pages" and document.
+ int limit = _provider.TextLength;
+ ValidateEndpoints();
+
+ // We'll move 1 format unit if we aren't already at the end of the
+ // document. Otherwise, we won't move at all.
+ moved = index < limit ? 1 : 0;
+ index = limit;
+ }
+ break;
+
+ default:
+ throw new InvalidEnumArgumentException(nameof(unit), (int)unit, typeof(TextUnit));
+ }
+
+ return index;
+ }
+
+ ///
+ /// Moves an endpoint backward a certain number of units.
+ ///
+ private int MoveEndpointBackward(int index, TextUnit unit, int count, out int moved)
+ {
+ switch (unit)
+ {
+ case TextUnit.Character:
+ {
+ ValidateEndpoints();
+ int oneBasedIndex = index + 1;
+ moved = Math.Max(count, -oneBasedIndex);
+ index = index + moved;
+ index = index < 0 ? 0 : index;
+ }
+ break;
+
+ case TextUnit.Word:
+ {
+ string text = _provider.Text;
+ ValidateEndpoints();
+
+ for (moved = 0; moved > count && index > 0; moved--)
+ {
+ index--;
+
+ while (!AtWordBoundary(text, index))
+ {
+ index--;
+ }
+ }
+ }
+ break;
+
+ case TextUnit.Line:
+ {
+ // Note count < 0.
+
+ // Get 1-based line.
+ int line = _provider.GetLineFromCharIndex(index) + 1;
+
+ int lineMax = _provider.LinesCount;
+
+ // Truncate the count to the number of available lines.
+ int actualCount = Math.Max(count, -line);
+
+ moved = actualCount;
+
+ if (actualCount == -line)
+ {
+ // We are moving by the maximum number of possible lines,
+ // so we know the resulting index will be 0.
+ index = 0;
+
+ // If a line other than the first consists of only "\r\n",
+ // you can move backwards past this line and the position changes,
+ // hence this is counted. The first line is special, though:
+ // if it is empty, and you move say from the second line back up
+ // to the first, you cannot move further; however if the first line
+ // is nonempty, you can move from the end of the first line to its
+ // beginning! This latter move is counted, but if the first line
+ // is empty, it is not counted.
+
+ // Recalculate the value of "moved".
+ // The first line is empty if it consists only of
+ // a line separator sequence.
+ bool firstLineEmpty = (lineMax == 0 || (lineMax > 1 && _provider.GetLineIndex(1) == LineSeparator.Length));
+ if (moved < 0 && firstLineEmpty)
+ {
+ ++moved;
+ }
+ }
+ else // actualCount > -line
+ {
+ // Move the endpoint to the beginning of the following line,
+ // then back by the line separator length to get to the end
+ // of the previous line, since the Edit control has
+ // no method to get the character index of the end
+ // of a line directly.
+ index = _provider.GetLineIndex(line + actualCount) - LineSeparator.Length;
+ }
+ }
+ break;
+
+ case TextUnit.Paragraph:
+ {
+ // Just like moving words but we look for paragraph boundaries instead of
+ // word boundaries.
+ string text = _provider.Text;
+ ValidateEndpoints();
+
+ for (moved = 0; moved > count && index > 0; moved--)
+ {
+ index--;
+
+ while (!AtParagraphBoundary(text, index))
+ {
+ index--;
+ }
+ }
+ }
+ break;
+
+ case TextUnit.Format:
+ case TextUnit.Page:
+ case TextUnit.Document:
+ {
+ // Since edit controls are plain text moving one uniform format unit will
+ // take us all the way to the beginning of the document, just like
+ // "pages" and document.
+
+ // We'll move 1 format unit if we aren't already at the beginning of the
+ // document. Otherwise, we won't move at all.
+ moved = index > 0 ? -1 : 0;
+ index = 0;
+ }
+ break;
+
+ default:
+ throw new InvalidEnumArgumentException(nameof(unit), (int)unit, typeof(TextUnit));
+ }
+
+ return index;
+ }
+
+ ///
+ /// Method to set both endpoints simultaneously.
+ ///
+ private void MoveTo(int start, int end)
+ {
+ _start = start >= 0 ? start : throw new ArgumentOutOfRangeException(nameof(start));
+ _end = end >= start ? end : throw new ArgumentOutOfRangeException(nameof(end));
+ }
+
+ private void ValidateEndpoints()
+ {
+ int limit = _provider.TextLength;
+
+ if (Start > limit && limit > 0)
+ {
+ Start = limit;
+ }
+
+ if (End > limit && limit > 0)
+ {
+ End = limit;
+ }
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/TestAccessors.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/TestAccessors.cs
index b1d4538691b..ea7f459824c 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/TestAccessors.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/TestAccessors.cs
@@ -2,8 +2,6 @@
// 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.Windows.Forms;
-
namespace System
{
///
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditClass.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditClass.cs
new file mode 100644
index 00000000000..30e24034f0a
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditClass.cs
@@ -0,0 +1,15 @@
+// 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
+{
+ internal class EditClass : WindowClass
+ {
+ public EditClass() : base(ComCtl32.WindowClasses.WC_EDIT)
+ {
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditControl.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditControl.cs
new file mode 100644
index 00000000000..92f789b272e
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/EditControl.cs
@@ -0,0 +1,24 @@
+// 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.Drawing;
+using static Interop.User32;
+
+namespace System
+{
+ internal class EditControl : Window
+ {
+ private static readonly EditClass s_editClass = new EditClass();
+
+ public EditControl(string windowName = default, ES editStyle = ES.LEFT,
+ WS style = WS.OVERLAPPED,
+ WS_EX extendedStyle = WS_EX.CLIENTEDGE | WS_EX.LEFT | WS_EX.LTRREADING,
+ bool isMainWindow = false, Window parentWindow = default,
+ IntPtr parameters = default, IntPtr menuHandle = default)
+ : base(s_editClass, new Rectangle(0, 0, 100, 50), windowName, style |= (WS)editStyle,
+ extendedStyle, isMainWindow, parentWindow, parameters, menuHandle)
+ {
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/Window.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/Window.cs
new file mode 100644
index 00000000000..bf7c79cc75e
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/Window.cs
@@ -0,0 +1,54 @@
+// 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.Drawing;
+using static Interop;
+
+namespace System
+{
+ internal class Window : IDisposable, IHandle
+ {
+ private readonly WindowClass _windowClass;
+
+ public IntPtr Handle { get; }
+
+ public Window(
+ WindowClass windowClass,
+ Rectangle bounds,
+ string windowName = default,
+ User32.WS style = User32.WS.OVERLAPPED,
+ User32.WS_EX extendedStyle = User32.WS_EX.DEFAULT,
+ bool isMainWindow = false,
+ Window parentWindow = default,
+ IntPtr parameters = default,
+ IntPtr menuHandle = default)
+ {
+ _windowClass = windowClass;
+ if (!_windowClass.IsRegistered)
+ {
+ _windowClass.Register();
+ }
+
+ Handle = _windowClass.CreateWindow(
+ bounds,
+ windowName,
+ style,
+ extendedStyle,
+ isMainWindow,
+ parentWindow?.Handle ?? default,
+ parameters,
+ menuHandle);
+ }
+
+ public void Dispose()
+ {
+ if (Handle != IntPtr.Zero)
+ {
+ User32.DestroyWindow(this);
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/TestUtilities/WindowClass.cs b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/WindowClass.cs
similarity index 77%
rename from src/System.Windows.Forms.Primitives/tests/TestUtilities/WindowClass.cs
rename to src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/WindowClass.cs
index b1d203c4fdf..08007fcb227 100644
--- a/src/System.Windows.Forms.Primitives/tests/TestUtilities/WindowClass.cs
+++ b/src/System.Windows.Forms.Primitives/tests/TestUtilities/Win32/WindowClass.cs
@@ -2,7 +2,6 @@
// 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;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
@@ -29,7 +28,7 @@ public unsafe static extern IntPtr LoadIconW(
private readonly string _className;
private readonly string _menuName;
- public ushort Atom { get; private set; }
+ public Atom Atom { get; private set; }
public IntPtr MainWindow { get; private set; }
public IntPtr ModuleInstance { get; }
@@ -111,7 +110,7 @@ public unsafe WindowClass(
};
}
- public bool IsRegistered => Atom != 0;
+ public bool IsRegistered => Atom.IsValid || ModuleInstance == IntPtr.Zero;
public unsafe WindowClass Register()
{
@@ -122,11 +121,12 @@ public unsafe WindowClass Register()
if (!string.IsNullOrEmpty(_menuName))
_wndClass.lpszMenuName = menuName;
- ushort atom = User32.RegisterClassW(ref _wndClass);
- if (atom == 0)
+ Atom atom = User32.RegisterClassW(ref _wndClass);
+ if (!atom.IsValid)
{
throw new Win32Exception();
}
+
Atom = atom;
return this;
}
@@ -165,19 +165,52 @@ public unsafe IntPtr CreateWindow(
if (!IsRegistered)
throw new ArgumentException("Window class must be registered before using.");
- IntPtr window = User32.CreateWindowExW(
- dwExStyle: extendedStyle,
- lpClassName: (char*)Atom,
- lpWindowName: windowName,
- dwStyle: style,
- X: bounds.X,
- Y: bounds.Y,
- nWidth: bounds.Width,
- nHeight: bounds.Height,
- hWndParent: parentWindow,
- hMenu: menuHandle,
- hInst: IntPtr.Zero,
- lpParam: parameters);
+ IntPtr window;
+ if (Atom.IsValid)
+ {
+ window = User32.CreateWindowExW(
+ dwExStyle: extendedStyle,
+ lpClassName: (char*)Atom.ATOM,
+ lpWindowName: windowName,
+ dwStyle: style,
+ X: bounds.X,
+ Y: bounds.Y,
+ nWidth: bounds.Width,
+ nHeight: bounds.Height,
+ hWndParent: parentWindow,
+ hMenu: menuHandle,
+ hInst: IntPtr.Zero,
+ lpParam: parameters);
+ }
+ else
+ {
+ fixed (char* atom = _className)
+ {
+ window = User32.CreateWindowExW(
+ dwExStyle: extendedStyle,
+ lpClassName: atom,
+ lpWindowName: windowName,
+ dwStyle: style,
+ X: bounds.X,
+ Y: bounds.Y,
+ nWidth: bounds.Width,
+ nHeight: bounds.Height,
+ hWndParent: parentWindow,
+ hMenu: menuHandle,
+ hInst: IntPtr.Zero,
+ lpParam: parameters);
+ }
+ }
+
+ if (window == IntPtr.Zero)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ if (!Atom.IsValid)
+ {
+ Atom = User32.GetClassLong(window, User32.GCL.ATOM);
+ }
if (isMainWindow)
MainWindow = window;
diff --git a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/TestAccessors.UiaTextRangeTestAccessor.cs b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/TestAccessors.UiaTextRangeTestAccessor.cs
new file mode 100644
index 00000000000..950e7335216
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/TestAccessors.UiaTextRangeTestAccessor.cs
@@ -0,0 +1,71 @@
+// 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.Windows.Forms.Automation;
+using static Interop;
+using static Interop.User32;
+
+namespace System
+{
+ public static partial class TestAccessors
+ {
+ internal class UiaTextRangeTestAccessor : TestAccessor
+ {
+ // Accessor for static members
+ private static readonly dynamic Static = typeof(UiaTextRange).TestAccessor().Dynamic;
+
+ public UiaTextRangeTestAccessor(UiaTextRange instance)
+ : base(instance) { }
+
+ public int _start
+ {
+ get => Dynamic._start;
+ set => Dynamic._start = value;
+ }
+
+ public int _end
+ {
+ get => Dynamic._end;
+ set => Dynamic._end = value;
+ }
+
+ public UiaTextProvider _provider => Dynamic._provider;
+
+ public CapStyle GetCapStyle(ES editStyle) => Dynamic.GetCapStyle(editStyle);
+
+ public double GetFontSize(LOGFONTW logfont) => Dynamic.GetFontSize(logfont);
+
+ public HorizontalTextAlignment GetHorizontalTextAlignment(ES editStyle) => Dynamic.GetHorizontalTextAlignment(editStyle);
+
+ public bool GetReadOnly() => Dynamic.GetReadOnly();
+
+ public void MoveTo(int start, int end) => Dynamic.MoveTo(start, end);
+
+ public void ValidateEndpoints() => Dynamic.ValidateEndpoints();
+
+ public bool AtParagraphBoundary(string text, int index) => Static.AtParagraphBoundary(text, index);
+
+ public bool AtWordBoundary(string text, int index) => Static.AtWordBoundary(text, index);
+
+ public COLORREF GetBackgroundColor() => Static.GetBackgroundColor();
+
+ public string GetFontName(LOGFONTW logfont) => Static.GetFontName(logfont);
+
+ public bool IsApostrophe(char ch) => Static.IsApostrophe(ch);
+
+ public Gdi32.FW GetFontWeight(LOGFONTW logfont) => Static.GetFontWeight(logfont);
+
+ public COLORREF GetForegroundColor() => Static.GetForegroundColor();
+
+ public bool GetItalic(LOGFONTW logfont) => Static.GetItalic(logfont);
+
+ public TextDecorationLineStyle GetStrikethroughStyle(LOGFONTW logfont) => Static.GetStrikethroughStyle(logfont);
+
+ public TextDecorationLineStyle GetUnderlineStyle(LOGFONTW logfont) => Static.GetUnderlineStyle(logfont);
+ }
+
+ internal static UiaTextRangeTestAccessor TestAccessor(this UiaTextRange textRange)
+ => new UiaTextRangeTestAccessor(textRange);
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs
new file mode 100644
index 00000000000..e51465e329f
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextProviderTests.cs
@@ -0,0 +1,150 @@
+// 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 Moq;
+using Xunit;
+using static Interop.User32;
+
+namespace System.Windows.Forms.Primitives.Tests.Automation
+{
+ public class UiaTextProviderTests
+ {
+ [StaFact]
+ public void UiaTextProvider_GetEditStyle_ContainsMultilineStyle_ForMultilineTextBox()
+ {
+ // EditControl Multiline is true when EditControl has ES_MULTILINE style
+ using EditControl textBox = new EditControl(
+ editStyle: ES.MULTILINE | ES.LEFT | ES.AUTOHSCROLL | ES.AUTOVSCROLL,
+ style: WS.OVERLAPPED | WS.VISIBLE);
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ ES actual = providerMock.Object.GetEditStyle(textBox.Handle);
+ Assert.True(actual.HasFlag(ES.MULTILINE));
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetEditStyle_DoesntContainMultilineStyle_ForSinglelineTextBox()
+ {
+ // EditControl Multiline is false when EditControl doesn't have ES_MULTILINE style
+ using EditControl textBox = new EditControl(
+ editStyle: ES.LEFT | ES.AUTOHSCROLL | ES.AUTOVSCROLL,
+ style: WS.OVERLAPPED | WS.VISIBLE);
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ ES actual = providerMock.Object.GetEditStyle(textBox.Handle);
+ Assert.False(actual.HasFlag(ES.MULTILINE));
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetEditStyle_ReturnsLeft_WithoutHandle()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ ES actual = providerMock.Object.GetEditStyle(IntPtr.Zero);
+ Assert.Equal(ES.LEFT, actual);
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetWindowStyle_ContainsVisible()
+ {
+ using EditControl textBox = new EditControl(
+ editStyle: ES.MULTILINE | ES.LEFT | ES.AUTOHSCROLL | ES.AUTOVSCROLL,
+ style: WS.OVERLAPPED | WS.VISIBLE);
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ WS actual = providerMock.Object.GetWindowStyle(textBox.Handle);
+ Assert.True(actual.HasFlag(WS.VISIBLE));
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetWindowStyle_ReturnsDisabled_WithoutHandle()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ WS actual = providerMock.Object.GetWindowStyle(IntPtr.Zero);
+ Assert.Equal(WS.DISABLED, actual);
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetWindowExStyle_ContainsClientedge()
+ {
+ using EditControl textBox = new EditControl(
+ style: WS.OVERLAPPED | WS.VISIBLE);
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ WS_EX actual = providerMock.Object.GetWindowExStyle(textBox.Handle);
+ Assert.True(actual.HasFlag(WS_EX.CLIENTEDGE));
+ }
+
+ [StaFact]
+ public void UiaTextProvider_GetWindowExStyle_ReturnsLeft_WithoutHandle()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+ WS_EX actual = providerMock.Object.GetWindowExStyle(IntPtr.Zero);
+ Assert.Equal(WS_EX.LEFT, actual);
+ }
+
+ [StaFact]
+ public void UiaTextProvider_RectArrayToDoubleArray_ReturnsCorrectValue()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ double[] expected = { 0, 0, 10, 5, 10, 10, 20, 30 };
+ double[] actual = providerMock.Object.RectListToDoubleArray(new List
+ {
+ new Rectangle(0, 0, 10, 5),
+ new Rectangle(10, 10, 20, 30)
+ });
+
+ Assert.Equal(8, actual.Length);
+
+ for (int i = 0; i < expected.Length; i++)
+ {
+ Assert.Equal(expected[i], actual[i]);
+ }
+ }
+
+#pragma warning disable CS8625 // RectArrayToDoubleArray doesn't accept a null parameter
+ [StaFact]
+ public void UiaTextProvider_RectArrayToDoubleArray_NullParameter_ReturnsNull()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ double[] actual = providerMock.Object.RectListToDoubleArray(null);
+ Assert.Empty(actual);
+ }
+#pragma warning restore CS8625
+
+ [StaFact]
+ public void UiaTextProvider_RectArrayToDoubleArray_EmptyArrayParameter_ReturnsEmptyArrayResult()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ double[] actual = providerMock.Object.RectListToDoubleArray(new List());
+ Assert.Empty(actual);
+ }
+
+ [StaFact]
+ public unsafe void UiaTextProvider_SendInput_SendsOneInput()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+ INPUT keyboardInput = new INPUT();
+
+ int actual = providerMock.Object.SendInput(1, ref keyboardInput, sizeof(INPUT));
+ Assert.Equal(1, actual);
+ }
+
+ [StaFact]
+ public void UiaTextProvider_SendKeyboardInputVK_SendsOneInput()
+ {
+ Mock providerMock = new Mock(MockBehavior.Strict);
+
+ int actual = providerMock.Object.SendKeyboardInputVK(VK.LEFT, true);
+ Assert.Equal(1, actual);
+ }
+ }
+}
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
new file mode 100644
index 00000000000..ff1c6e65365
--- /dev/null
+++ b/src/System.Windows.Forms.Primitives/tests/UnitTests/System/Windows/Forms/Automation/UiaTextRangeTests.cs
@@ -0,0 +1,1106 @@
+// 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.UiaCore;
+using static Interop.Gdi32;
+using System.Runtime.InteropServices;
+using Moq;
+using static Interop.User32;
+
+namespace System.Windows.Forms.Primitives.Tests.Automation
+{
+ public class UiaTextRangeTests
+ {
+ // Used to get access to the test accessor for static members
+ private const UiaTextRange StaticNullTextRange = null!;
+
+ [StaTheory]
+ [InlineData(0, 0)]
+ [InlineData(0, 5)]
+ [InlineData(5, 10)]
+ [InlineData(1, 1)]
+ public void UiaTextRange_Constructor_InitializesProvider_And_CorrectEndpoints(int start, int end)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start, end);
+ Assert.Equal(start, textRange.Start);
+ Assert.Equal(end, textRange.End);
+ Assert.Equal(enclosingElement, ((ITextRangeProvider)textRange).GetEnclosingElement());
+
+ object actual = textRange.TestAccessor()._provider;
+
+ Assert.Equal(provider, actual);
+ }
+
+ [StaTheory]
+ [InlineData(-10, 0, 0, 0)]
+ [InlineData(0, -10, 0, 0)]
+ [InlineData(5, 0, 5, 5)]
+ [InlineData(-1, -1, 0, 0)]
+ [InlineData(10, 5, 10, 10)]
+ [InlineData(-5, 5, 0, 5)]
+ [InlineData(5, -5, 5, 5)]
+ public void UiaTextRange_Constructor_InitializesProvider_And_CorrectEndpoints_IfEndpointsincorrect(int start, int end, int expectedStart, int expectedEnd)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start, end);
+ Assert.Equal(expectedStart, textRange.Start);
+ Assert.Equal(expectedEnd, textRange.End);
+ }
+
+#pragma warning disable CS8625 // UiaTextRange constructor doesn't accept a provider null parameter
+ [StaFact]
+ public void UiaTextRange_Constructor_Provider_Null_ThrowsException()
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ Assert.Throws(() => new UiaTextRange(enclosingElement, null, 0, 5));
+ }
+#pragma warning restore CS8625
+
+#pragma warning disable CS8625 // UiaTextRange constructor doesn't accept an enclosingElement null parameter
+ [StaFact]
+ public void UiaTextRange_Constructor_Control_Null_ThrowsException()
+ {
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ Assert.Throws(() => new UiaTextRange(null, provider, 0, 5));
+ }
+#pragma warning restore CS8625
+
+ [StaTheory]
+ [InlineData(3, -5)]
+ [InlineData(-5, 3)]
+ [InlineData(-3, -5)]
+ public void UiaTextRange_Constructor_SetCorrectValues_IfNegativeStartEnd(int start, int end)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start, end);
+ Assert.True(textRange.Start >= 0);
+ Assert.True(textRange.End >= 0);
+ }
+
+ [StaTheory]
+ [InlineData(0)]
+ [InlineData(5)]
+ [InlineData(int.MaxValue)]
+ public void UiaTextRange_End_Get_ReturnsCorrectValue(int end)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 0, end);
+ Assert.Equal(end, textRange.End);
+ }
+
+ [StaTheory]
+ [InlineData(0)]
+ [InlineData(5)]
+ [InlineData(int.MaxValue)]
+ public void UiaTextRange_End_SetCorrectly(int end)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 0, end: 0);
+ textRange.End = end;
+ int actual = textRange.End < textRange.Start ? textRange.Start : textRange.End;
+ Assert.Equal(end, actual);
+ }
+
+ [StaFact]
+ public void UiaTextRange_End_SetCorrect_IfValueIncorrect()
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 5, end: 10);
+ textRange.End = 3; /*Incorrect value*/
+ Assert.Equal(textRange.Start, textRange.End);
+
+ textRange.End = 6;
+ Assert.Equal(6, textRange.End);
+
+ textRange.End = -10; /*Incorrect value*/
+ Assert.Equal(textRange.Start, textRange.End);
+ }
+
+ [StaTheory]
+ [InlineData(0, 0, 0)]
+ [InlineData(5, 5, 0)]
+ [InlineData(3, 15, 12)]
+ [InlineData(0, 10, 10)]
+ [InlineData(6, 10, 4)]
+ public void UiaTextRange_Length_ReturnsCorrectValue(int start, int end, int expected)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start, end);
+ Assert.Equal(expected, textRange.Length);
+ }
+
+ [StaTheory]
+ [InlineData(-5, 0)]
+ [InlineData(0, -5)]
+ [InlineData(-5, -5)]
+ [InlineData(10, 5)]
+ public void UiaTextRange_Length_ReturnsCorrectValue_IfIncorrectStartEnd(int start, int end)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, 3, 10);
+
+ var testAccessor = textRange.TestAccessor();
+ testAccessor._start = start;
+ testAccessor._end = end;
+
+ Assert.Equal(0, textRange.Length);
+ }
+
+ [StaTheory]
+ [InlineData(0)]
+ [InlineData(5)]
+ [InlineData(int.MaxValue)]
+ public void UiaTextRange_Start_Get_ReturnsCorrectValue(int start)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 0, end: 0);
+
+ textRange.TestAccessor()._start = start;
+
+ Assert.Equal(start, textRange.Start);
+ }
+
+ [StaTheory]
+ [InlineData(0)]
+ [InlineData(5)]
+ [InlineData(int.MaxValue)]
+ public void UiaTextRange_Start_SetCorrectly(int start)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 0, end: 0);
+ textRange.Start = start;
+ int actual = textRange.Start < textRange.End ? textRange.End : textRange.Start;
+ Assert.Equal(start, actual);
+ }
+
+ [StaFact]
+ public void UiaTextRange_Start_Set_Correct_IfValueIncorrect()
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 4, end: 8);
+ textRange.Start = -10;
+ Assert.Equal(0, textRange.Start);
+ Assert.Equal(8, textRange.End);
+ }
+
+ [StaFact]
+ public void UiaTextRange_Start_Set_Correct_IfValueMoreThanEnd()
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 4, end: 10);
+ textRange.Start = 15; // More than End = 10
+ Assert.True(textRange.Start <= textRange.End);
+ }
+
+ [StaFact]
+ public void UiaTextRange_ITextRangeProvider_Clone_ReturnsCorrectValue()
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange = new UiaTextRange(enclosingElement, provider, start: 3, end: 9);
+ UiaTextRange actual = (UiaTextRange)((ITextRangeProvider)textRange).Clone();
+ Assert.Equal(textRange.Start, actual.Start);
+ Assert.Equal(textRange.End, actual.End);
+ }
+
+ [StaTheory]
+ [InlineData(3, 9, true)]
+ [InlineData(0, 2, false)]
+ public void UiaTextRange_ITextRangeProvider_Compare_ReturnsCorrectValue(int start, int end, bool expected)
+ {
+ IRawElementProviderSimple enclosingElement = new Mock(MockBehavior.Strict).Object;
+ UiaTextProvider provider = new Mock(MockBehavior.Strict).Object;
+ UiaTextRange textRange1 = new UiaTextRange(enclosingElement, provider, start: 3, end: 9);
+ UiaTextRange textRange2 = new UiaTextRange(enclosingElement, provider, start, end);
+ bool actual = ((ITextRangeProvider)textRange1).Compare(textRange2).IsTrue();
+ Assert.Equal(expected, actual);
+ }
+
+ public static IEnumerable