Skip to content

Commit

Permalink
Implement TextPattern support for Text controls
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
vladimir-krestov committed Sep 7, 2020
1 parent 7545756 commit f2baffc
Show file tree
Hide file tree
Showing 15 changed files with 1,792 additions and 48 deletions.
3 changes: 3 additions & 0 deletions src/System.Windows.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ public partial class AccessibleObject :
UiaCore.ISelectionItemProvider,
UiaCore.IRawElementProviderHwndOverride,
UiaCore.IScrollItemProvider,
UiaCore.IMultipleViewProvider
UiaCore.IMultipleViewProvider,
UiaCore.ITextProvider,
UiaCore.ITextProvider2
{
/// <summary>
/// Specifies the <see cref='IAccessible'/> interface used by this <see cref='AccessibleObject'/>.
Expand All @@ -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,
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}

/// <summary>
/// Performs custom navigation between parent/child/sibling accessible
/// objects. This is basically just a wrapper for GetSysChild(), that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

/// <summary>
/// Create a new wrapper.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
};
}
Expand Down Expand Up @@ -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();

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2257,7 +2257,7 @@ private RichTextBoxSelectionAttribute GetCharFormat(CFM mask, CFE effect)
return charFormat;
}

Font GetCharFormatFont(bool selectionOnly)
private Font GetCharFormatFont(bool selectionOnly)
{
ForceHandleCreate();

Expand Down Expand Up @@ -3237,6 +3237,8 @@ private void UserPreferenceChangedHandler(object o, UserPreferenceChangedEventAr
}
}

protected override AccessibleObject CreateAccessibilityInstance() => new ControlAccessibleObject(this);

/// <summary>
/// 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
Expand Down
42 changes: 42 additions & 0 deletions src/System.Windows.Forms/src/System/Windows/Forms/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading

0 comments on commit f2baffc

Please sign in to comment.