问题
I need to retrieve the index of the caret inside a textbox in the focused window, maybe using UI Automation, or maybe a Win32 API function, if there's any function that cat do that. And I emphasize, I don't mean the x,y coordinates, but the index of the caret inside the text of the textbox. How can I do that? Also see this similar question.
回答1:
You can use UI Automation for that, and especially the IUIAutomationTextPattern2 interface that has a GetCaretRange method.
Here is a sample Consoleapp C++ code that runs continuously and displays the caret position for the current element under the mouse:
C++
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#
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 (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 has caret issue with IME (Input Method Editor, like the Emoji editor you can summon using Win+; keyboard combination). There's no problem with wordpad for example.
来源:https://stackoverflow.com/questions/61368790/how-can-i-get-the-caret-position-from-a-textbox-in-another-application-not-the