Text before the caret: no UIA TextPattern on classic Win32 edit controls, fall back to ValuePattern and EM_GETSEL
TextPattern gives the caret and the text before it in most modern controls, but classic Win32 edit boxes expose no TextPattern; a fallback reads ValuePattern and asks the control for its selection start with EM_GETSEL, and browser ranges must be clamped to the field.
windowsui-automationtextpatternvaluepatternem-getselcomtypespython History
Goal
Before pasting dictated text, the app wants up to 160 characters that precede the caret in the focused control, so it can continue an unfinished sentence (lower-case start, comma where needed). The focused control is found with UI Automation (IUIAutomation.GetFocusedElement).
TextPattern path
For modern controls the app asks the element for UIA_TextPatternId, then:
GetSelection(); no selection ranges means "unknown".- The caret range is the selection's first range. Only an editable field counts:
GetAttributeValue(UIA_IsReadOnlyAttributeId)must be exactlyFalse. A web page or document that is being read also reports a caret, and its text is not wanted. - A cloned range is moved so that it ends at the start of the caret and starts
charscharacters earlier (MoveEndpointByRange,MoveEndpointByUnitwith a negative count). - The text is read from that range and CRLF is normalised.
Clamp to the field in browsers
In a browser, moving the start endpoint back carries on past the beginning of the field into the page around it. A dictation into an empty box then started with ", " because the page text before the field was picked up as "text before the caret". The fix (changelog, 2026-11-13) compares the range start with the field's DocumentRange start and moves it back to the field start when it lies before it; if the clamped range is empty the result is an empty string, meaning "start of the field".
Classic edit controls have no TextPattern
When GetCurrentPattern(UIA_TextPatternId) returns nothing, the element is often a classic Win32 Edit control (WinForms text boxes are an example the author tested). Those controls expose ValuePattern (the whole text) but no caret position through UI Automation. The fallback:
- Require a native window handle (
CurrentNativeWindowHandle), aValuePattern, and a class name containing "edit". - Skip read-only controls (
CurrentIsReadOnly). - Send
EM_GETSEL(0x00B0) withSendMessageTimeoutW, flagSMTO_ABORTIFHUNGand a 200 ms timeout, so a hung target cannot block the dictation. The selection start is in the low word of the result. - If the low word is 0xFFFF the position is treated as unknown (the code comment says it is beyond what this form of the message reports), otherwise the text is
value[:start][-chars:].
EM_GETSEL is a message to the control, so it works only for controls that implement it; custom-drawn or rich controls that expose neither pattern simply return "unknown" and the dictation is pasted without continuation logic.
Related reading-side behaviour
- A separate "read the whole field" helper tries
TextPattern.DocumentRange.GetText(max)first andValuePattern.CurrentValuesecond, and gives up on fields of 200,000 characters or more. - Terminals and some custom controls expose neither pattern; the helper returns
Nonerather than guessing.
Unverified
- Only a WinForms text box was verified directly for the fallback. The notes list Outlook, Teams and other browsers as untested at the time of the first version, and a browser case was fixed afterwards.
- The statement that "classic Win32 edit boxes have no TextPattern" is the author's observation on the tested control, not a survey of every edit-control variant (for example rich edit controls).
Sources
- Read from the source of a Python dictation tray app (comtypes, UI Automation) and its project notes, 2026-11-12 to 2026-11-13. Tested by the author against a WinForms text box and by scripted calls; Outlook, Teams and browsers were noted as untested at the time of the first version, and a later changelog entry fixes a browser case.