Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17


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

Posted on 2008-06-25
Medium Priority
Last Modified: 2008-08-15
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?
Question by:soriega
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 4
  • 3
  • 2
  • +2

Expert Comment

by:Christopher Martinez
ID: 21870545

Expert Comment

ID: 21870562
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.....

Author Comment

ID: 21870597
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)
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.


Expert Comment

ID: 21870652
You probably want to make use of as well then.

Author Comment

ID: 21870818
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


Expert Comment

ID: 21870894
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.
LVL 10

Expert Comment

ID: 21872990
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.
LVL 17

Expert Comment

ID: 21888098
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?

Expert Comment

ID: 21894726
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:

Author Comment

ID: 21900109
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?

LVL 17

Accepted Solution

zzzzzooc earned 1600 total points
ID: 21948441
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


Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

When trying to find the cause of a problem in VBA or VB6 it's often valuable to know what procedures were executed prior to the error. You can use the Call Stack for that but it is often inadequate because it may show procedures you aren't intereste…
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
Show developers how to use a criteria form to limit the data that appears on an Access report. It is a common requirement that users can specify the criteria for a report at runtime. The easiest way to accomplish this is using a criteria form that a…
This lesson covers basic error handling code in Microsoft Excel using VBA. This is the first lesson in a 3-part series that uses code to loop through an Excel spreadsheet in VBA and then fix errors, taking advantage of error handling code. This l…
Suggested Courses

715 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question