简体   繁体   English

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

[英]Get tooltips text from C# with PInvoke

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. 我在C#中使用PInvoke,尝试读取具有已知处理程序的窗口中可见的工具提示,但是我尝试以此方式检查的Windows应用程序因内存访问冲突错误而崩溃,或者只是没有在其中显示工具提示文本lpszText TOOLINFO成员。

I'm calling EnumWindows with a callback and then sending a message to the tooltip window in that function: 我用回调函数调用EnumWindows ,然后在该函数的工具提示窗口中发送一条消息:

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

Here's how I defined the TOOLINFO structure: 这是我定义TOOLINFO结构的方法:

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

the TTM_GETTEXT value TTM_GETTEXT值

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

and the SendMessage overload 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? 因此,我的代码中是否缺少任何明显的错误,我应该更改什么才能解决这种情况?

Edit: Here is the whole code, so you could test. 编辑: 是完整的代码,因此您可以进行测试。

You are sending a private message across processes, which requires manual marshaling. 您正在跨进程发送私人消息,这需要手动封送处理。 Here's another stackoverflow question on the same topic . 这是关于同一主题的另一个stackoverflow问题 Better would be to change direction entirely and use Active Accessibility and/or UI Automation, which are designed for this sort of thing. 最好是完全改变方向,并使用针对此类情况设计的Active Accessibility和/或UI Automation。

I ended up using UI Automation, as Raymond suggested. 正如Raymond建议的那样,我最终使用了UI Automation。 AutomationElement , who's Name property value contains the text in case of tooltips, proved to be exactly what the code required. 在工具提示的情况下, AutomationElementName属性值包含文本,事实证明这正是所需的代码。 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: 我循环浏览所有工具提示所在的所有Desktop子窗口,并且只在鼠标下显示属于拥有该窗口的进程的子窗口:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM