【问题标题】:How can I get the caret position from a textbox in another application? (Not the coordinates, but the actual index inside of the textbox)如何从另一个应用程序的文本框中获取插入符号的位置? (不是坐标,而是文本框内的实际索引)
【发布时间】:2020-04-22 15:21:56
【问题描述】:

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

【问题讨论】:

  • 您添加的链接中的答案有什么问题?这是要走的路
  • @SimonMourier 看看帖子的 cmets,这种方法可以帮助我只收到 x,y 坐标,正如我所提到的,这不是我需要的。
  • 如果文本框是标准的 Windows 文本框,那么您可以向文本框发送EM_CHARFROMPOS 窗口消息,以将客户端坐标转换为字符索引。或者,向其发送EM_GETSEL 窗口消息以获取当前选定文本的开始/结束索引,其中一个将与插入符号位置相同。
  • EM_CHARFROMPOS 总是返回 0 @RemyLebeau
  • @TalXD 您是否先将坐标从屏幕坐标转换为客户端坐标?

标签: c# winapi automation mouseevent


【解决方案1】:

您可以为此使用 UI 自动化,尤其是具有 GetCaretRange 方法的 IUIAutomationTextPattern2 接口。

以下是两个示例控制台应用程序(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)
  • 某些应用程序错误地报告插入符号,尤其是当您选择文本时(因此,使用移动的插入符号)。例如,使用记事本,在按下 SHIFT 的同时移动 LEFT 将向后选择,但由于某种原因 UIA 不会更新插入符号位置。我认为这是记事本的问题,因为它也有 IME 的插入符号问题(输入法编辑器,例如您可以使用 Win+; 键盘组合召唤的表情符号编辑器),这也使用全局插入符号位置。例如,写字板没有问题。

【讨论】:

  • 我可以在 c# 中做同样的事情吗?也谢谢! @SimonMourier
  • 我复制了你的代码,由于某种原因 vs 没有找到像 CUIAutomation8 和 IUIAutomationTextPattern2 这样的类
  • @TalXD - 您是否将 UIAutomationClient dll 引用为 COM 对象,而不是使用标准的 .NET 程序集?您使用的是哪个 Windows 版本?
  • win 10,大约一个小时前,我尝试了您的代码。但由于某种原因,它在谷歌浏览器上不起作用,它可能不支持 AutomationUI。谢谢!!!
  • @TalXD - chrome 可以广泛配置为一般自动化:stackoverflow.com/questions/47216824/…
猜你喜欢
  • 2019-03-06
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 2011-05-31
  • 1970-01-01
  • 1970-01-01
  • 2011-11-17
  • 2021-12-16
相关资源
最近更新 更多