How can I get the caret position from a textbox in another application? (Not the coordinates, but the actual index inside of the textbox)
See the question and my original answer on StackOverflowYou can use UI Automation for that, and especially the IUIAutomationTextPattern2 interface that has a GetCaretRange method.
Here are two sample Console app (C++ and C# code) that run continuously and display the caret position for the current element under the mouse:
C++ version
int main()
{
CoInitializeEx(NULL, COINIT_MULTITHREADED);
{
CComPtr<IUIAutomation> automation;
// make sure you use CLSID_CUIAutomation8, *not* CLSID_CUIAutomation
automation.CoCreateInstance(CLSID_CUIAutomation8);
do
{
POINT pt;
if (GetCursorPos(&pt))
{
CComPtr<IUIAutomationElement> element;
automation->ElementFromPoint(pt, &element);
if (element)
{
CComBSTR name;
element->get_CurrentName(&name);
wprintf(L"Watched element %s\n", name);
CComPtr<IUIAutomationTextPattern2> text;
element->GetCurrentPatternAs(UIA_TextPattern2Id, IID_PPV_ARGS(&text));
if (text)
{
// get document range
CComPtr<IUIAutomationTextRange> documentRange;
text->get_DocumentRange(&documentRange);
// get caret range
BOOL active = FALSE;
CComPtr<IUIAutomationTextRange> range;
text->GetCaretRange(&active, &range);
if (range)
{
// compare caret start with document start
int caretPos = 0;
range->CompareEndpoints(TextPatternRangeEndpoint_Start, documentRange, TextPatternRangeEndpoint_Start, &caretPos);
wprintf(L" caret is at %i\n", caretPos);
}
}
}
}
Sleep(500);
} while (TRUE);
}
CoUninitialize();
return 0;
}
C# version
static void Main(string[] args)
{
// needs 'using UIAutomationClient;'
// to reference UIA, don't use the .NET assembly
// but instead, reference the UIAutomationClient dll as a COM object
// and set Embed Interop Types to False for the UIAutomationClient reference in the C# project
var automation = new CUIAutomation8();
do
{
var cursor = System.Windows.Forms.Cursor.Position;
var element = automation.ElementFromPoint(new tagPOINT { x = cursor.X, y = cursor.Y });
if (element != null)
{
Console.WriteLine("Watched element " + element.CurrentName);
var guid = typeof(IUIAutomationTextPattern2).GUID;
var ptr = element.GetCurrentPatternAs(UIA_PatternIds.UIA_TextPattern2Id, ref guid);
if (ptr != IntPtr.Zero)
{
var pattern = (IUIAutomationTextPattern2)Marshal.GetObjectForIUnknown(ptr);
if (pattern != null)
{
var documentRange = pattern.DocumentRange;
var caretRange = pattern.GetCaretRange(out _);
if (caretRange != null)
{
var caretPos = caretRange.CompareEndpoints(
TextPatternRangeEndpoint.TextPatternRangeEndpoint_Start,
documentRange,
TextPatternRangeEndpoint.TextPatternRangeEndpoint_Start);
Console.WriteLine(" caret is at " + caretPos);
}
}
}
}
Thread.Sleep(500);
}
while (true);
}
The trick is to use the IUIAutomationTextRange::CompareEndpoints method that allows you to compare the caret range with another range, for example the whole document range.
Note there are drawbacks:
- some apps don't support any MSAA/UIA introspection at all, or don't support the text pattern. For these, there are simply no solution (even using Windows API I think)
- some apps report the caret incorrectly, especially when you select text (so, with a moving caret). For example with Notepad, moving LEFT while pressing SHIFT will select backwards but for some reason UIA doesn't update caret pos. I think it's a problem with Notepad because it also has caret issue with IME (Input Method Editor, like the Emoji editor you can summon using Win+; keyboard combination) which also uses the global caret position. There's no problem with Wordpad for example.