Solved

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

Posted on 2008-06-25
13
1,160 Views
Last Modified: 2008-08-15
Hi
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?
0
Comment
Question by:soriega
  • 4
  • 3
  • 2
  • +2
13 Comments
 
LVL 7

Expert Comment

by:Christopher Martinez
Comment Utility
0
 
LVL 3

Expert Comment

by:GHCS_Mark
Comment Utility
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.....
0
 

Author Comment

by:soriega
Comment Utility
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)
0
 
LVL 3

Expert Comment

by:GHCS_Mark
Comment Utility
You probably want to make use of http://msdn.microsoft.com/en-us/library/ms633558.aspx as well then.
0
 

Author Comment

by:soriega
Comment Utility
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

0
Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

 
LVL 3

Expert Comment

by:GHCS_Mark
Comment Utility
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.
0
 
LVL 10

Expert Comment

by:peetm
Comment Utility
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.
0
 
LVL 17

Expert Comment

by:zzzzzooc
Comment Utility
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?
0
 
LVL 3

Expert Comment

by:GHCS_Mark
Comment Utility
As you said, you would need to allocate memory from the Global Heap and then copy it to a local before destroying the allocation.

http://msdn.microsoft.com/en-us/library/aa302340.aspx#win32map_memorymanagementfunctions

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

http://www.vbforums.com/showthread.php?t=527756
0
 

Author Comment

by:soriega
Comment Utility
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?

Thanks
0
 
LVL 17

Accepted Solution

by:
zzzzzooc earned 400 total points
Comment Utility
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.



Form1:
------------------------------------------------------------
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



Module1:
------------------------------------------------------------
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
        Next
        'cleanup
        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


0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Introduction While answering a recent question about filtering a custom class collection, I realized that this could be accomplished with very little code by using the ScriptControl (SC) library.  This article will introduce you to the SC library a…
Since upgrading to Office 2013 or higher installing the Smart Indenter addin will fail. This article will explain how to install it so it will work regardless of the Office version installed.
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
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…

763 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

Need Help in Real-Time?

Connect with top rated Experts

9 Experts available now in Live!

Get 1:1 Help Now