如何从另一个应用程序的文本框中获取插入符位置? (不是坐标,而是文本框内的实际索引)

How can I get the caret position from a textbox in another application? (Not the coordinates, but the actual index inside of the textbox)

我需要在焦点 window 的文本框中检索插入符号的索引,可能使用 UI 自动化,或者可能是 Win32 API 函数,如果有任何函数那只猫那样做。 我要强调的是,我指的不是 x,y 坐标,而是文本框文本中插入符号的索引。我怎样才能做到这一点? 另见 this 类似问题。

您可以为此使用 UI 自动化,尤其是 IUIAutomationTextPattern2 interface that has a GetCaretRange 方法。

这是两个示例控制台应用程序(C++ 和 C# 代码),它们 运行 连续显示鼠标下当前元素的插入符号位置:

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);
}

诀窍是使用 IUIAutomationTextRange::CompareEndpoints 方法,该方法允许您将插入符号范围与另一个范围(例如整个文档范围)进行比较。

注意有缺点:

  • 有些应用根本不支持任何 MSAA/UIA 自省,或者不支持文本模式。对于这些,根本没有解决方案(我认为即使使用 Windows API)
  • 一些应用程序错误地报告了插入符,尤其是当您 select 文本(因此,带有移动的插入符)时。例如,对于记事本,在按下 SHIFT 的同时移动 LEFT 将 select 向后移动,但由于某些原因 UIA 不会更新插入符位置。我认为这是记事本的问题,因为它也有 IME 的插入符号问题(输入法编辑器,就像你可以使用 Win+; 键盘组合),它也使用全局插入符位置。例如写字板就没有问题。