• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1214
  • Last Modified:

How do i retrieve the tooltip text at a specific spot on the screen?

I am trying to retrieve the text from an application and need to get the text out of the tooltipballoon. I have tried to use FindwindowsEX, Gettext(), childenumerations etc. However, i can't find the hwnd at the point where i wan't to get the tooltip balloon from.
SO: is it possible to simply place the mouse at a specific point on the screen, wait for the balloon to appear and then read what it says without knowing the hwnd of the underlying windowscontrol?
  • 4
  • 3
  • 2
  • +2
1 Solution
Christopher MartinezCommented:
I am presuming you are trying to do this for any application on the system.  You would therefore need to find the location of the mouse on the screen and perform some form of hittest to find the hwnd that the mouse was over.  You could then find the class of that window and see if it was a balloontip as I'm sure they will have a specific style but as I have never attempted this, I couldn't be any less vague.....
soriegaAuthor Commented:
OK i looked at the TTM_GETTEXT, it seems however like it still requires to know the hwnd (handle) of the control you wan't to retreive from. I can't find the hwnd, so what i need is basically to retreive the tooltiptext that shows up at a specific point X , Y on the screen (and that's not necessarily in a hwnd i can find the name of)
Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

You probably want to make use of http://msdn.microsoft.com/en-us/library/ms633558.aspx as well then.
soriegaAuthor Commented:
Thanks for WindowFromPoint. I can't get it to find any window though. In fact, wherever i place the mouse on the screen, it allways returns the same handle. Please look below.
     Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
    Private Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As POINTAPI) As Integer
   Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) 
Handles Button4.Click
        Dim result As Long, lptext As String
        Dim structCursorPosition As POINTAPI
        Call GetCursorPos(structCursorPosition)
        Debug.Print("X: " & structCursorPosition.x & " Y: " & structCursorPosition.y)
        'here is where the window is returned...
        result = WindowFromPoint(structCursorPosition.x, structCursorPosition.y)
    End Sub

Open in new window

According to the documentation that I linked, WindowFromPoint expects a POINT structure, not just x/y points as parameters, and therefore this could be why its failing to find the correct window.
I think I'd use a combination of EnumWindows/EnumChildWindows/GetClassName - to find TOOLTIPS_CLASSA class windows.  I'd then check with GetWindowRect and WindowFromPoint whether *this* window was the one I wanted.  Then it's a case of using TTM_GETTEXT.
For those suggesting to simply use TTM_GETTEXT, can you provide examples?

Sending TTM_GETTEXT to a window belonging to a different process will result in that process attempting to fill the TOOLINFO structure located within the virtual memory of your own process -- which it does not have access to.

To accomplish this, you'll need to allocate virtual memory that both processes have access to, and read/write your variables/structures there for when you use SendMessage().

I could not get this to work in XP and I don't know why it failed either. :-) Although I did try this in VB6 and not .NET but I'm not entirely sure of the best method of approaching this with .NET. Maybe native Win32 API or Marshal class?
As you said, you would need to allocate memory from the Global Heap and then copy it to a local before destroying the allocation.


Take a look here for an example using a listview of how to access items from another process:

soriegaAuthor Commented:
Thanks far all help. I still need some example code on how to retreive the text using SENDMESSAGE and TTM_GETTEXT (as zzzzzooc pointed out..)

Does anyone have some code for this?

Played around with it some more and I found out why it wasn't working (in VB6). I guessed wrong when I thought lParam probably wouldn't be required in the TOOLINFO structure. :-(

Now the below is only a start and not fully reliable. Some tooltips aren't being retrieved via either TTM_ENUMTOOLS/TTM_GETTEXT for some reason. MSDN mentioned using GETDISPINFO (WM_NOTIFY) but I haven't poked around with that so that could help. It's also only supported on Windows 2000 and above because Read/WriteProcessMemory() isn't supported on 9x. I don't have access to those systems so that makes it difficult to test anything for those platforms. :-)

It's in VB6 but conversion shouldn't be that difficult.

Option Explicit

Private Type POINTAPI
    x As Long
    y As Long
End Type

Private Declare Function WindowFromPoint Lib "user32" (ByVal xPoint As Long, ByVal yPoint As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long

Private m_currwnd As Long
Private Sub Form_Load()
    Timer1.Interval = 3000
    Timer1.Enabled = True
End Sub
Private Sub Timer1_Timer()
    Dim pt As POINTAPI
    Dim wnd As Long
    Call GetCursorPos(pt)
    wnd = WindowFromPoint(pt.x, pt.y)
    If (wnd <> m_currwnd) Then
        m_currwnd = wnd
        Call List1.Clear
        Call FindToolTips(m_currwnd)
    End If
End Sub

Option Explicit

Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    Bottom As Long
End Type

Private Type TOOLINFO
    cbSize      As Long
    uFlags      As Long
    hWnd        As Long
    uId         As Long
    RECT        As RECT
    hInst       As Long
    lpszText    As Long
    lParam      As Long
    'lpReserved  As Long
End Type

Private Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Private Declare Function EnumThreadWindows Lib "user32.dll" (ByVal dwThreadId As Long, ByVal lpfn As Long, ByVal lParam As Long) As Long
Private Declare Function GetCurrentProcessId Lib "kernel32.dll" () As Long
Private Declare Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hWnd As Long, ByRef lpdwProcessId As Long) As Long
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function VirtualAllocEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFreeEx Lib "kernel32" (ByVal hProcess As Long, lpAddress As Any, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long

Private Const PAGE_READWRITE = &H4&

Private Const MEM_RESERVE = &H2000
Private Const MEM_COMMIT = &H1000
Private Const MEM_RELEASE = &H8000

Private Const PROCESS_VM_OPERATION As Long = (&H8)
Private Const PROCESS_VM_READ As Long = (&H10)
Private Const PROCESS_VM_WRITE As Long = (&H20)

Private Const WM_USER As Long = &H400
Private Const TTM_GETTOOLCOUNT As Long = (WM_USER + 13)
Private Const TTM_ENUMTOOLSA = (WM_USER + 14)
Private Const TTM_ENUMTOOLSW = (WM_USER + 58)
Private Const TTM_GETTEXTA As Long = (WM_USER + 11)
Private Const TTM_GETTEXTW = (WM_USER + 56)
Public Sub FindToolTips(ByVal owner_hWnd As Long)
    Dim thread As Long
    thread = GetWindowThreadProcessId(owner_hWnd, ByVal 0&)
    If (thread <> 0) Then
        Call EnumThreadWindows(thread, AddressOf EnumThreadWndProc, 0)
    End If
End Sub
Public Function EnumThreadWndProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
    Const LPSTR_TEXTCALLBACK As Long = -1
    Const TOOLTIP_MAXLEN As Long = 160
    Dim numtools As Long
    Dim m_pid As Long
    Dim m_proc As Long
    Dim m_ptr_ti As Long
    Dim m_ptr_txt As Long
    Dim m_len_ti As Long
    Dim ti As TOOLINFO
    Dim i As Integer
    Dim pos As Integer
    Dim buff() As Byte
    Dim buff_str As String
    'get number of tools.. if none, probably not tooltip control
    numtools = SendMessage(hWnd, TTM_GETTOOLCOUNT, 0, ByVal 0&)
    If (numtools > 0) Then
        'open target process, allocate memory
        Call GetWindowThreadProcessId(hWnd, m_pid)
        m_proc = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, m_pid)
        m_len_ti = LenB(ti)
        m_ptr_ti = VirtualAllocEx(m_proc, 0, m_len_ti, MEM_COMMIT Or MEM_RESERVE, PAGE_READWRITE)
        m_ptr_txt = VirtualAllocEx(m_proc, 0, TOOLTIP_MAXLEN, MEM_COMMIT Or MEM_RESERVE, PAGE_READWRITE)
        'loop through tools
        For i = 0 To (numtools - 1)
            ti.cbSize = m_len_ti
            ti.lpszText = m_ptr_txt
            'initialize some stuff :P
            ReDim buff(TOOLTIP_MAXLEN - 1) As Byte
            Call WriteProcessMemory(m_proc, ByVal m_ptr_ti, ti, m_len_ti, 0)
            Call WriteProcessMemory(m_proc, ByVal m_ptr_txt, buff(0), TOOLTIP_MAXLEN, 0)
            'get tool info
            If (SendMessage(hWnd, TTM_ENUMTOOLSW, i, ByVal m_ptr_ti)) Then
                Call ReadProcessMemory(m_proc, ByVal m_ptr_ti, ti, m_len_ti, 0)
                'if ptr changed to -1 (callback), use gettext (msdn says use GETDISPINFO?)
                If (ti.lpszText = LPSTR_TEXTCALLBACK) Then
                    ti.lpszText = m_ptr_txt
                    Call WriteProcessMemory(m_proc, ByVal m_ptr_ti, ti, m_len_ti, 0)
                    Call SendMessage(hWnd, TTM_GETTEXTW, ByVal TOOLTIP_MAXLEN, ByVal m_ptr_ti)
                End If
                Call ReadProcessMemory(m_proc, ByVal m_ptr_txt, ByVal VarPtr(buff(0)), TOOLTIP_MAXLEN, 0)
                'format our text
                buff_str = buff
                pos = InStr(1, buff_str, vbNullChar)
                If (pos > 0) Then
                    buff_str = Left$(buff_str, pos - 1)
                End If
                'your tooltip...
                If (buff_str <> vbNullString) Then
                    Form1.List1.AddItem buff_str
                End If
            End If
        Call VirtualFreeEx(m_proc, ByVal m_ptr_ti, m_len_ti, MEM_RELEASE)
        Call VirtualFreeEx(m_proc, ByVal m_ptr_txt, TOOLTIP_MAXLEN, MEM_RELEASE)
        Call CloseHandle(m_proc)
    End If
    'continue enum
    EnumThreadWndProc = 1
End Function

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

  • 4
  • 3
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now