使用PInvoke从C#获取工具提示文本

使用PInvoke从C#获取工具提示文本,c#,pinvoke,tooltip,sendmessage,C#,Pinvoke,Tooltip,Sendmessage,我在C#中使用PInvoke,试图通过已知的处理程序读取窗口中可见的工具提示,但我尝试以这种方式检查的窗口的应用程序会因内存访问冲突错误而崩溃,或者干脆不在lpszText TOOLINFO成员中显示工具提示文本 我通过回调调用,然后向该函数中的工具提示窗口发送消息: public delegate bool CallBackPtr(IntPtr hwnd, IntPtr lParam); static void Main(string[] args) { callBackPtr = ne

我在C#中使用PInvoke,试图通过已知的处理程序读取窗口中可见的工具提示,但我尝试以这种方式检查的窗口的应用程序会因内存访问冲突错误而崩溃,或者干脆不在lpszText TOOLINFO成员中显示工具提示文本

我通过回调调用,然后向该函数中的工具提示窗口发送消息:

public delegate bool CallBackPtr(IntPtr hwnd, IntPtr lParam);

static void Main(string[] args)
{
  callBackPtr = new CallBackPtr(Report);

  IntPtr hWnd = WindowFromPoint(<mouse coordinates point>);

  if (hWnd != IntPtr.Zero)
  {
        Console.Out.WriteLine("Window with handle " + hWnd +
                              " and class name " +
                              getWindowClassName(hWnd));

        EnumWindows(callBackPtr, hWnd);

        Console.Out.WriteLine();
  }


  public static bool Report(IntPtr hWnd, IntPtr lParam)
  {
        String windowClassName = getWindowClassName(hWnd);

        if (windowClassName.Contains("tool") &&
             GetParent(hWnd) == lParam)
        {
            string szToolText = new string(' ', 250);

            TOOLINFO ti = new TOOLINFO();
            ti.cbSize = Marshal.SizeOf(typeof(TOOLINFO));
            ti.hwnd = GetParent(hWnd);
            ti.uId = hWnd;
            ti.lpszText = szToolText;

            SendMessage(hWnd, TTM_GETTEXT, (IntPtr)250, ref ti);

            Console.WriteLine("Child window handle is " + hWnd + " and class name " + getWindowClassName(hWnd) + " and value " + ti.lpszText);
        }

        return true;
    }
TTM_GETTEXT值

private static UInt32 WM_USER = 0x0400;
private static UInt32 TTM_GETTEXT = (WM_USER + 56);
超负荷

那么,我的代码中是否有任何明显的错误,我应该做些什么来解决这个问题呢


编辑:是整个代码,因此您可以进行测试。

您正在跨进程发送私有消息,这需要手动封送处理。更好的做法是完全改变方向,使用主动可访问性和/或UI自动化,这是为这类事情而设计的。

您正在跨进程发送私人消息,这需要手动封送处理。最好是完全改变方向,使用主动可访问性和/或UI自动化,这是为这类事情设计的。

正如Raymond所建议的那样,我最终使用了UI自动化
AutomationElement
,who's
Name
属性值包含工具提示时的文本,事实证明这正是代码所需的内容。我在桌面的所有子窗口中循环,所有工具提示都位于其中,我只在鼠标下显示属于拥有该窗口的进程的工具提示:

    public static bool Report(IntPtr hWnd, IntPtr lParam)
    {
        if (getWindowClassName(hWnd).Contains("tool"))
        {
            AutomationElement element = AutomationElement.FromHandle(hWnd);
            string value = element.Current.Name;

            if (value.Length > 0)
            {
                uint currentWindowProcessId = 0;
                GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);

                if (element.Current.ProcessId == currentWindowProcessId)
                    Console.WriteLine(value);
            }
        }

        return true;
    }


    static void Main(string[] args)
    {
        callBackPtr = new CallBackPtr(Report);

        do
        {
            System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF

            currentWindowHWnd = WindowFromPoint(mouse);
            if (currentWindowHWnd != IntPtr.Zero)
                EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);

            Thread.Sleep(1000);
        }
        while (true);
    }

正如雷蒙德建议的那样,我最终使用了UI自动化
AutomationElement
,who's
Name
属性值包含工具提示时的文本,事实证明这正是代码所需的内容。我在桌面的所有子窗口中循环,所有工具提示都位于其中,我只在鼠标下显示属于拥有该窗口的进程的工具提示:

    public static bool Report(IntPtr hWnd, IntPtr lParam)
    {
        if (getWindowClassName(hWnd).Contains("tool"))
        {
            AutomationElement element = AutomationElement.FromHandle(hWnd);
            string value = element.Current.Name;

            if (value.Length > 0)
            {
                uint currentWindowProcessId = 0;
                GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);

                if (element.Current.ProcessId == currentWindowProcessId)
                    Console.WriteLine(value);
            }
        }

        return true;
    }


    static void Main(string[] args)
    {
        callBackPtr = new CallBackPtr(Report);

        do
        {
            System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF

            currentWindowHWnd = WindowFromPoint(mouse);
            if (currentWindowHWnd != IntPtr.Zero)
                EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);

            Thread.Sleep(1000);
        }
        while (true);
    }

这是一个正确的问题,你应该提供你所有的代码。许多P/Invoke函数丢失(WindowFromPoint等)。@Simon Mourier它们丢失是因为它们与我的问题无关;那些遗漏了预期的工作,我已经分离出了给我带来麻烦的确切代码子集。@Luviere-它们不是无关紧要的,因为我们无法测试您的代码来帮助您。至少放一个异常堆栈框架。@Simon Mourier从这个角度看,你是对的,我编辑了这个问题,最后包含了一个指向整个代码的链接。这是一个正确的问题,你应该提供你所有的代码。许多P/Invoke函数丢失(WindowFromPoint等)。@Simon Mourier它们丢失是因为它们与我的问题无关;那些遗漏了预期的工作,我已经分离出了给我带来麻烦的确切代码子集。@Luviere-它们不是无关紧要的,因为我们无法测试您的代码来帮助您。至少放一个异常堆栈框架。@Simon Mourier从这个角度看,你是对的,我编辑了这个问题,以便在最后包含一个指向整个代码的链接。UI自动化需要目标应用程序的合作,不幸的是,我没有。主动访问性似乎是Windows中当前UI自动化框架的先驱,因此它也可能对我没有帮助。除此之外,我编写的代码是基于C++编写的一个所谓的功能性解决方案。我在这里对一个问题的回答中发现:该解决方案假设目标窗口属于同一进程。(如果您意识到缓冲区需要如何封送,这是“显而易见的”)并且不清楚什么场景需要您阅读其他应用程序的工具提示。您使用UI自动化的想法是正确的,请参阅我的答案以获取生成的代码。对于我来说,编组过程的低层次细节确实是一个陌生的话题,目前已不再构成使其工作的障碍。感谢您的帮助。UI自动化需要目标应用程序的合作,不幸的是,我没有。主动访问性似乎是Windows中当前UI自动化框架的先驱,因此它也可能对我没有帮助。除此之外,我编写的代码是基于C++编写的一个所谓的功能性解决方案。我在这里对一个问题的回答中发现:该解决方案假设目标窗口属于同一进程。(如果您意识到缓冲区需要如何封送,这是“显而易见的”)并且不清楚什么场景需要您阅读其他应用程序的工具提示。您使用UI自动化的想法是正确的,请参阅我的答案以获取生成的代码。对于我来说,编组过程的低层次细节确实是一个陌生的话题,目前已不再构成使其工作的障碍。谢谢你的帮助。
    public static bool Report(IntPtr hWnd, IntPtr lParam)
    {
        if (getWindowClassName(hWnd).Contains("tool"))
        {
            AutomationElement element = AutomationElement.FromHandle(hWnd);
            string value = element.Current.Name;

            if (value.Length > 0)
            {
                uint currentWindowProcessId = 0;
                GetWindowThreadProcessId(currentWindowHWnd, out currentWindowProcessId);

                if (element.Current.ProcessId == currentWindowProcessId)
                    Console.WriteLine(value);
            }
        }

        return true;
    }


    static void Main(string[] args)
    {
        callBackPtr = new CallBackPtr(Report);

        do
        {
            System.Drawing.Point mouse = System.Windows.Forms.Cursor.Position; // use Windows forms mouse code instead of WPF

            currentWindowHWnd = WindowFromPoint(mouse);
            if (currentWindowHWnd != IntPtr.Zero)
                EnumChildWindows((IntPtr)0, callBackPtr, (IntPtr)0);

            Thread.Sleep(1000);
        }
        while (true);
    }