# Accessibility and UI Automation

> Automate the WPF TextControl with UI Automation: TextPattern attributes, ValuePattern, EM_REPLACESEL to the AutomationId window handle, and ribbon key tips.

- **Product:** TX Text Control .NET for WPF
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET for WPF > Integration Guide > Accessibility and UI Automation
- **3803 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/accessibility/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/accessibility/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/accessibility/llms-full.txt

---

Assistive technologies such as screen readers and speech recognition, and tools for automated UI tests, work with a WPF window through Microsoft UI Automation. For most WPF controls, WPF provides this interface itself; the WPF `TextControl` implements its own, which exposes the text of the document with the formatting of each part. Through this interface, an automation client finds the editor and reads and changes its text; your window adds key tips and keyboard support for users who work without a mouse.

### What the TextControl Exposes

The WPF `TextControl` has implemented UI Automation since version 25.0, and since version 27.0 it supports two control patterns, in WPF as in Windows Forms:

- **TextPattern** exposes the text of the document in ranges, and each range reports its format and style attributes, such as the font, the size, bold and italic, the alignment and the indents.
- **ValuePattern** represents the complete text as one string, which a client can read and replace.

The blog article [Sneak Peek X15: UI Automation Interface for TX Text Control .NET for WPF](https://www.textcontrol.com/blog/2017/05/25/sneak-peek-x15-ui-automation-interface-for-txtextcontrol-net-for-wpf/), written before version 25.0 was released, also names the SynchronizedInputPattern, and describes accessibility and automated UI testing as the two uses of the interface.

A client recognizes the WPF `TextControl` by these properties of its automation element:

| Property | Value |
|---|---|
| `LocalizedControlType` | `"document"` |
| `ClassName` | `"TXTextControl.WPF.TextControl"` |
| `FrameworkId` | `"WPF"` |

The TextPattern supports `DocumentRange`, `GetSelection`, `GetVisibleRanges` and `RangeFromPoint`, and raises `TextSelectionChangedEvent` when the input position changes. Its text ranges support `GetText`, `Select`, `GetBoundingRectangles`, `GetEnclosingElement`, `ScrollIntoView`, `Clone`, `Compare`, `CompareEndpoints`, `ExpandToEnclosingUnit`, `Move`, `MoveEndpointByUnit`, `MoveEndpointByRange` and `GetAttributeValue`.

### Text Attributes a Client Can Read

`GetAttributeValue` returns these attributes of a text range:

| Attributes | TextPattern fields |
|---|---|
| Colors | `BackgroundColorAttribute`, `ForegroundColorAttribute` |
| Font | `FontNameAttribute`, `FontSizeAttribute`, `FontWeightAttribute`, `IsItalicAttribute`, `IsSubscriptAttribute`, `IsSuperscriptAttribute`, `StrikethroughStyleAttribute`, `UnderlineStyleAttribute` |
| Paragraph | `HorizontalTextAlignmentAttribute`, `IndentationFirstLineAttribute`, `IndentationLeadingAttribute`, `IndentationTrailingAttribute`, `TabsAttribute`, `TextFlowDirectionsAttribute` |
| Page | `MarginBottomAttribute`, `MarginLeadingAttribute`, `MarginTopAttribute`, `MarginTrailingAttribute` |
| Other | `CultureAttribute`, `IsReadOnlyAttribute` |

`StyleNameAttribute` is not supported. A range whose characters differ in an attribute, such as partly bold text, returns `TextPattern.MixedAttributeValue` for it.

### Read the Editor From Another Application

A UI Automation client is a separate application. The following console application finds the `TextControl` in the running application *MyEditor*, prints its text, reports whether the selection is bold and writes a line whenever the input position moves. It uses the namespace `System.Windows.Automation`, which a .NET project references with `<UseWPF>true</UseWPF>` and a target framework such as `net8.0-windows`:

```csharp
using System;
using System.Diagnostics;
using System.Windows.Automation;
using System.Windows.Automation.Text;

// Find the main window of the running editor application
Process editor = Process.GetProcessesByName("MyEditor")[0];
AutomationElement window = AutomationElement.FromHandle(editor.MainWindowHandle);

// Find the first element that exposes its text through the TextPattern
AutomationElement textControl = window.FindFirst(TreeScope.Descendants,
    new PropertyCondition(AutomationElement.IsTextPatternAvailableProperty, true));

// Identify the element as a TextControl
string className = textControl.Current.ClassName;
Console.WriteLine($"{className}, {textControl.Current.FrameworkId}, {textControl.Current.LocalizedControlType}");

if (className == "TXTextControl.TextControl" || className == "TXTextControl.WPF.TextControl")
{
    TextPattern textPattern = (TextPattern)textControl.GetCurrentPattern(TextPattern.Pattern);

    // The complete text of the document
    Console.WriteLine(textPattern.DocumentRange.GetText(-1));

    // Is the selected text bold?
    TextPatternRange selection = textPattern.GetSelection()[0];
    object fontWeight = selection.GetAttributeValue(TextPattern.FontWeightAttribute);
    bool isBold = fontWeight != TextPattern.MixedAttributeValue && (int)fontWeight == 700;
    Console.WriteLine($"Selection is bold: {isBold}");

    // Report every move of the input position
    Automation.AddAutomationEventHandler(TextPattern.TextSelectionChangedEvent,
        textControl, TreeScope.Element,
        (sender, e) => Console.WriteLine("The selection has changed."));

    Console.ReadLine();
}
```

The code works as follows:

- **`FindFirst`** with `IsTextPatternAvailableProperty` finds the first element of the window that exposes its text, which is the editor in a window with one `TextControl`.
- **`ClassName`** distinguishes the WPF `TextControl` from the one of Windows Forms; the client works with both.
- **`DocumentRange.GetText(-1)`** returns the whole text, and **`GetAttributeValue`** the attribute of a range: 700 for bold text.
- **`TextSelectionChangedEvent`** occurs whenever the input position in the editor changes.

An automated UI test uses the same calls to compare the document with the expected result.

### Change the Text From Outside

The ValuePattern replaces the complete text:

```csharp
// The ValuePattern replaces the whole text of the document
if (textControl.TryGetCurrentPattern(ValuePattern.Pattern, out object pattern))
{
    ((ValuePattern)pattern).SetValue("New text");
}
```

To insert text at the input position or replace the selection, a client sends the Win32 edit control messages `EM_GETSEL`, `EM_SETSEL` and `EM_REPLACESEL`, which the `TextControl` handles. A WPF control has no window handle of its own, but the automation element of the WPF `TextControl` provides the handle of a wrapper window as its `AutomationId`:

```csharp
IntPtr hWnd = new IntPtr(int.Parse(textControl.Current.AutomationId));
```

`EM_REPLACESEL` inserts the text at the input position when nothing is selected. In the Windows Forms `TextControl`, which is a native window, `textControl.Current.NativeWindowHandle` gives the handle instead.

#### Send EM\_REPLACESEL to the Editor

A .NET client imports the Win32 functions it needs from *user32.dll*. The following class declares the three message numbers, `SendMessageTimeout` to send a message with a time limit, and `IsWindow` to check the handle first:

```csharp
using System;
using System.Runtime.InteropServices;

internal static class Win32
{
    internal const int EM_GETSEL = 0x00B0;
    internal const int EM_SETSEL = 0x00B1;
    internal const int EM_REPLACESEL = 0x00C2;

    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    internal static extern IntPtr SendMessageTimeout(IntPtr hWnd, int uMsg, IntPtr wParam,
        IntPtr lParam, int flags, int uTimeout, out IntPtr result);

    [DllImport("user32.dll", ExactSpelling = true)]
    internal static extern bool IsWindow(IntPtr hWnd);
}
```

With the handle from the `AutomationId`, the client in the console application above inserts text at the input position of the editor, or in place of its selection:

```csharp
using System.Runtime.InteropServices;

IntPtr hWnd = new IntPtr(int.Parse(textControl.Current.AutomationId));

if (Win32.IsWindow(hWnd))
{
    // Copy the text into unmanaged memory as a Unicode string
    IntPtr text = Marshal.StringToHGlobalUni("New text");
    try
    {
        // Flag 1 (SMTO_BLOCK), wait at most 10 seconds
        IntPtr timeResult = Win32.SendMessageTimeout(hWnd, Win32.EM_REPLACESEL,
            IntPtr.Zero, text, 1, 10000, out IntPtr messageResult);

        if (timeResult == IntPtr.Zero)
            Console.WriteLine("The editor did not answer in time.");
        else
            Console.WriteLine(messageResult == (IntPtr)1 ? "Text inserted." : "Text not inserted.");
    }
    finally
    {
        Marshal.FreeHGlobal(text);
    }
}
```

The code works as follows:

- **`Marshal.StringToHGlobalUni`** copies the string into a buffer of unmanaged memory, and `lParam` passes its address. Windows copies the buffer into the process of the editor, because `EM_REPLACESEL` is a system message. `FreeHGlobal` releases the buffer afterward.
- **`SendMessageTimeout`** returns `IntPtr.Zero` when the message failed or the 10,000 milliseconds passed before the editor processed it. Flag 1, `SMTO_BLOCK`, keeps the calling thread from processing other requests until the function returns.
- **`messageResult`** is the answer of the `TextControl`: 1 when the text was inserted and 0 when it was not.

`EM_GETSEL` and `EM_SETSEL`, which read and set the selection, are sent with the same function. Microsoft documents [SendMessageTimeout](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessagetimeouta), its flags and the edit control messages.

### Work Without a Mouse

- **In the document**, the `TextControl` supports the keyboard shortcuts of a word processor. [Keyboard Shortcuts](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/document-editor/editing-text/keyboard-shortcuts/llms-full.txt) lists them.
- **In the ribbon**, the WPF `Ribbon` shows key tips when the user presses **Alt**. The items of the predefined tabs of TX Text Control have key tips. A `RibbonButton` of your own gets the key tip of a built-in command from [ResourceProvider.GetKeyTip](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-wpf/resourceprovider/getkeytip-method/llms-full.txt):

```csharp
string print = ResourceProvider.FileMenuItem.TXITEM_Print.ToString();
RibbonButton printButton = new RibbonButton()
{
    Label = ResourceProvider.GetText(print),
    KeyTip = ResourceProvider.GetKeyTip(print)
};
```

`KeyTip` is a property of the WPF `RibbonButton`; `GetKeyTip` returns the key tip in the language of the user interface. [Custom Tabs and Commands](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/document-editor/ribbon-ui/custom-tabs/llms-full.txt) adds such buttons to a tab.

### Next Steps

- [DPI and Display Scaling](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/dpi-scaling/llms-full.txt): a sharp editor at large scale factors.
- [Keyboard Shortcuts](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/document-editor/editing-text/keyboard-shortcuts/llms-full.txt): the keys of the editor.
- [Commands and Input Bindings](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/commands/llms-full.txt): keyboard commands of your own for the window.
- [PDF/A](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/pdf-a/llms-full.txt): archivable and accessible PDF documents created from the editor's content.

---

## Nearby

- **Previous:** [DPI and Display Scaling](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/dpi-scaling/llms-full.txt)
- **Next:** [AI Coding Assistants](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/integration/coding-assistants/llms-full.txt)
