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

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

问题描述:

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

I'm using PInvoke in C#, trying to read tooltips visible in a window with a known handler, but the apps who's windows I try to inspect in this manner crash with memory access violation errors, or simply don't reveal the tooltip text in the lpszText TOOLINFO member.

我正在通过回调调用 EnumWindows ,然后将消息发送至该功能的工具提示窗口:

I'm calling EnumWindows with a callback and then sending a message to the tooltip window in that function:

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

这是我定义TOOLINFO结构的方式:

Here's how I defined the TOOLINFO structure:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    private int _Left;
    private int _Top;
    private int _Right;
    private int _Bottom;
}


struct TOOLINFO
{
    public int cbSize;
    public int uFlags;
    public IntPtr hwnd;
    public IntPtr uId;
    public RECT rect;
    public IntPtr hinst;

    [MarshalAs(UnmanagedType.LPTStr)]
    public string lpszText;

    public IntPtr lParam;
}

TTM_GETTEXT值

the TTM_GETTEXT value

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

SendMessage 重载

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, ref TOOLINFO lParam);

那么,我的代码中是否存在任何明显的错误,我应该更改什么才能解决这种情况?

So, is there any obvious error that I'm missing in my code, what should I change so that this situation is resolved?

此处是整个代码,因此您可以进行测试.

Here is the whole code, so you could test.

正如Raymond建议的那样,我最终使用了UI自动化.在工具提示的情况下,AutomationElement(其属性为Name)包含文本,事实证明这正是所需的代码.我循环浏览所有工具提示所在的所有Desktop子窗口,并且只在鼠标下显示属于拥有该窗口的进程的子窗口:

I ended up using UI Automation, as Raymond suggested. AutomationElement, who's Name property value contains the text in case of tooltips, proved to be exactly what the code required. I'm cycling through all the Desktop's child windows, where all the tooltips reside and I only display those that belong to the process that owns the window under the mouse:

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