Accessibility and UI Automation
Screen readers, speech recognition software and automated UI tests don't see your application the way a user does: they ask Windows for its elements through Microsoft UI Automation. The TextControl answers these requests with the text of its document and the formatting of each part, and it can be named, reached by keyboard and changed from outside. Here is what TX Text Control .NET for Windows Forms exposes, how an accessibility tool or a test finds and reads the editor, and what your application adds on its side.
What the TextControl Exposes
Since version 27.0, the TextControl implements two control patterns of UI Automation, in Windows Forms and in WPF:
- TextPattern gives access to 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.
A UI Automation client recognizes the TextControl by these properties of its automation element:
| Property | Value |
|---|---|
LocalizedControlType | "document" |
ClassName | "TXTextControl.TextControl" |
FrameworkId | "WinForm" |
The TextPattern of the TextControl supports DocumentRange, GetSelection, GetVisibleRanges and RangeFromPoint for getting text ranges, and raises TextSelectionChangedEvent when the input position changes. On a range, it supports GetText, Select, GetBoundingRectangles, GetEnclosingElement, ScrollIntoView, Clone, Compare, CompareEndpoints, ExpandToEnclosingUnit, Move, MoveEndpointByUnit, MoveEndpointByRange and GetAttributeValue.
Text Attributes a Client Can Read
GetAttributeValue of a text range returns these attributes of the text in the TextControl:
| 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. For a range whose characters differ in an attribute, such as partly bold text, the value is TextPattern.MixedAttributeValue.
Read the Editor From Another Application
The following console application reads the TextControl of a running application named MyEditor: it prints the text of the document, reports whether the selected text is bold and writes a line whenever the user moves the input position. It uses the UI Automation client of .NET, in the namespace System.Windows.Automation; in a .NET project, set <UseWPF>true</UseWPF> and a target framework such as net8.0-windows in the project file to reference it.
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:
AutomationElement.FromHandlegets the element of the main window, andFindFirstsearches its descendants for the first element that supports the TextPattern.ClassNametells aTextControlof Windows Forms from one of WPF, so the same client works with both.DocumentRange.GetText(-1)returns the whole text;-1means no length limit.GetAttributeValue(TextPattern.FontWeightAttribute)returns 700 for bold text andMixedAttributeValuefor a selection that is partly bold.TextSelectionChangedEventis raised by theTextControlwhenever the input position changes.
The same calls serve automated UI tests: a test starts the application, finds the editor this way and compares its text and attributes with the expected result.
Change the Text From Outside
The ValuePattern replaces the complete text of the document:
// The ValuePattern replaces the whole text of the document
if (textControl.TryGetCurrentPattern(ValuePattern.Pattern, out object pattern))
{
((ValuePattern)pattern).SetValue("New text");
}
The ValuePattern has no method to insert text at the input position or to replace the selection. For that, the TextControl handles the standard edit control messages of Win32, EM_GETSEL, EM_SETSEL and EM_REPLACESEL, which a client sends to the window of the editor. In Windows Forms, the TextControl is a native window, and its handle is the NativeWindowHandle property of the automation element:
IntPtr hWnd = new IntPtr(textControl.Current.NativeWindowHandle);
EM_REPLACESEL inserts the text at the input position when nothing is selected. Microsoft documents the messages and their parameters.
To send a message, the client imports two functions of user32.dll, SendMessageTimeout and IsWindow, and defines the numbers of the three messages:
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;
[]
internal static extern IntPtr SendMessageTimeout(IntPtr hwnd, int uMsg, IntPtr wParam,
IntPtr lParam, int flags, int uTimeout, out IntPtr result);
[]
internal static extern bool IsWindow(IntPtr hWnd);
}
With this class, the client inserts text at the input position of the TextControl, or replaces the selected text, with the handle hWnd from above:
if (Win32.IsWindow(hWnd))
{
// Copy the text into an unmanaged buffer
IntPtr text = Marshal.StringToHGlobalUni("New text");
// Wait at most 10 seconds for the editor to process the message
IntPtr timeResult = Win32.SendMessageTimeout(hWnd, Win32.EM_REPLACESEL,
IntPtr.Zero, text, 1, 10000, out IntPtr messageResult);
Marshal.FreeHGlobal(text);
bool inserted = timeResult != IntPtr.Zero && messageResult.ToInt32() == 1;
}
The code works as follows:
IsWindowchecks that the handle still belongs to a window, for example after the application of the editor has been closed.Marshal.StringToHGlobalUnicopies the string into unmanaged memory. The system passes this buffer on to the process of the editor, so the client needs no memory of its own in that process.Marshal.FreeHGlobalreleases the buffer afterward.SendMessageTimeoutreturnsIntPtr.Zerowhen the 10,000 milliseconds passed before the text was inserted.messageResultis 1 when the text was inserted and 0 when it was not.
Name the Editor for Screen Readers
The TextControl, the ButtonBar, the RulerBar and the StatusBar derive from System.Windows.Forms.Control and inherit its accessibility properties. Give an editor a name and a description when a form has more than one, so that a screen reader can tell them apart:
// Name and describe the editor for screen readers
textControl1.AccessibleName = "Letter";
textControl1.AccessibleDescription = "The text of the letter to the customer";
AccessibleName, AccessibleDescription and AccessibleRole are also in the Properties window, in the Accessibility category.
Work Without a Mouse
Users who can't use a mouse work with the keyboard alone:
- In the document, the
TextControlsupports the keyboard shortcuts of a word processor for moving, selecting, formatting and editing. Keyboard Shortcuts lists them. - In the ribbon, every item of the predefined tabs has a key tip, the letter the user presses after Alt to reach it. Give buttons of your own a key tip with KeyTip, or take the one of a built-in command from ResourceProvider.GetKeyTip. Tabs and groups have a KeyTip property as well.
Next Steps
- High DPI and Display Scaling: a sharp editor at large scale factors.
- Keyboard Shortcuts: the keys of the editor.
- Custom Tabs and Commands: ribbon buttons with key tips and tool tips.
- PDF/A: archivable and accessible PDF documents created from the editor's content.