21

I would like to get the tooltip text for win32 legacy control (not WPF controls that inherently support UI Automation).

Screenshot of the buttons

What I have done:

  • Given a button of interest, I've got its AutomationElement, and its bounding rect
  • I moved the mouse over this button (in code);
  • Thread.Sleep(1500) to wait for the tooltip control to popup;
  • Enumerate Desktop's all child windows, and get the child window tooltipAutomationElement, whose type is "Tooltip";
  • From tooltipAutomationElement, get this tooltip's name property, which corresponds to the tooltip string.

This actually works, but the penalty is: I have to sleep(1500) and manually wait for the tooltip to appear (5-20 buttons are to be scanned for the tooltip strings), which does not match performance requirement.

What is expected (not sure if it is feasible)

  • Programmatically get the button's tooltip string without requiring the tooltip to appear
  • Without having to place mouse over each button one-by-one.

Update 1: For TTN_NEEDTEXT, MSDN doc seems not very clear, and I have no clue how to program this using C#. One of the relevant link for low level structures/messages related to tooltip control can be found here.

Update 2: Those who believe this could be done by ... , I would say, it is easier said than done. I welcome those who have tried, to comment on this, and some ostensibly feasible solutions are welcome if you can offer some evidence to show its applicability and efficacy.

Update 3: If we try to minimize the TTM_SETDELAYTIME so that N in the sleep(N) can be minimized, this does not work after some experimentation. We can only adjust this once the tooltip window handle exists. e.g.

SendMessage(_tooltipCtrl.Handle, TTM_SETDELAYTIME, _TTDT_INITIAL, 10); //10 ms

Update 4: using TTM_GETTEXTA message seems to be a solution, however, it is similar to Update 3, where we need the handle of the tooltipCtrl, which is only available AFTER the tooltip is created, since to have this tooltip created, we have no choice but to hover mouse cursor above the tool, which seems to have performance issues (Thread.Sleep) as outlined above.

SendMessage(_tooltipCtrl.Handle, TTM_GETTEXTA, 0, ti);

Update 5: "How to get the tooltip text" using InterOp (PInvoke) or Automation UI using traditional approach (mouse hovering on the tool window, find the Hwnd handle, then get its text...) is not the concern of this post. What is expected: Can we extract the tooltip string of a control (say a button) with no need of hovering upon the control? If yes, how?

Update 6: using WM_MOUSEHOVER to activate the tooltip window seems not working, I have tested that out using SendMessage(...) with proper wparam and lparam filled, but in vein.

Cœur
  • 37,241
  • 25
  • 195
  • 267
David
  • 15,894
  • 22
  • 55
  • 66
  • 2
    It *might* not be possible. The control doesn't have to come up with tooltip text until the tooltip is actually shown. – Roger Lipscombe Apr 04 '13 at 10:43
  • @JimMischel, thanks, but note the comment below, which says "Looks interesting - unfortunately it's only sent by the tooltip if the tooltip's TOOLINFO::lpszText field was set to LPSTR_TEXTCALLBACK. So each time the tooltip is displayed, it will ask the control for the text to show. Maybe this is the common case (I hope so), but I suspect that there are still plenty of tooltips out there which have a static text (i.e. TOOLINFO::lpszText is set to a static string). – Frerich Raabe Aug 26 '09 at 14:38" – David Apr 04 '13 at 13:09
  • @David: Yes, I saw that. But there are a couple of other answers that look promising, one with code from the runtime library. – Jim Mischel Apr 04 '13 at 13:42
  • The accessible description may be close enough. – Raymond Chen Apr 06 '13 at 04:03
  • what do you mean by "The accessible description"? – David Apr 06 '13 at 05:08
  • 1
    You could try to change the tooltip (if it's a Windows tooltip) initialy delay using this TTM_SETDELAYTIME message: http://msdn.microsoft.com/library/windows/desktop/bb760404.aspx – Simon Mourier Apr 06 '13 at 12:35
  • Did you try to intercept the `TTM_ADDTOOL` message? – dyp Apr 06 '13 at 14:14
  • @Dyp: Do I have to create my own WndProc() function, and do a switch for TTM_ADDTOOL? – David Apr 06 '13 at 14:17
  • 1
    IIRC, you have to [install a hook into the application](http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx) in order to monitor events (intercepting is not necessary). – dyp Apr 06 '13 at 14:41
  • >>`win32 legacy control` The situation is not very clear -- is the tooltip created by a C/C++/MFC control that you are embedding in a c# Form? – Edward Clements Apr 07 '13 at 18:20
  • @EdwardClements, I think it is very clear, the legacy program is not written by me, I just wish to hook my logic into the process. That is why I used UI Automation. What is your confusion? I don't quite understand, and does this matter much? – David Apr 09 '13 at 09:27
  • http://cboard.cprogramming.com/windows-programming/102867-win32-api-tooltip-control.html this may be helpful. – A.P.S Apr 09 '13 at 10:23
  • 1
    Could the [`LoadString`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms647486(v=vs.85).aspx) function be helpful? – Alex Filipovici Apr 09 '13 at 15:04
  • @AlexFilipovici, can you expand a little bit more? It sounds interesting. – David Apr 09 '13 at 16:12
  • I am wondering why you do all this automation, what business request it serves. It seemed to me that you don't necessarily need to get the tooltips at runtime or you dont't need the legacy application running (or maybe you do?). Could you explain a little bit the broader context? Why do you need the tooltip strings and what are you supposed to use them for? – Alex Filipovici Apr 09 '13 at 17:03
  • Do you like it ? http://www.codeproject.com/Articles/6196/Building-a-BallonToolTip-provider-in-C –  Apr 09 '13 at 17:36
  • I think @AlexFilipovici is on to something - if you loop through all the string resources it should be easy to find where all the tooltips are stored... It's a bit of a hack - but it just might work. I have no target to test this with unfortunately – Floris Apr 10 '13 at 03:44
  • Isn't this a duplicate of question http://stackoverflow.com/questions/7512080/get-tooltips-text-from-c-sharp-with-pinvoke ? Seems to be a number o answers regarding this. I'm surprised this question comes up so often. – saarp Apr 10 '13 at 07:35
  • @saarp, if you had a careful read, you can see the difference. none of the approach you suggested worked. Please correct me if you disagree upon this. – David Apr 10 '13 at 07:42
  • @David - "none"? This is my first response. I didn't see any other comments referencing this. The answer in the question I posted seems like it directly returns the AutomationElement for the button from a mouse position. Doesn't this allow you to get the tooltip without waiting? – saarp Apr 10 '13 at 07:59
  • @Deanna - If you look at the answer (http://stackoverflow.com/a/7522883/741326) the sleep is only there to reduce mouse position sampling to 1/sec. No sleep is necessary. Maybe I should just submit the code as an answer. – saarp Apr 10 '13 at 12:53
  • @Deanna - don't worry about it. I'm not a jerk about these things. :) – saarp Apr 10 '13 at 13:35
  • @David That's OK :) I'll be removing my comments shortly and recomend you do the same so they aren't left without context – Deanna Apr 12 '13 at 09:18
  • What you want is basically impossible since the application can be built in a way that it decides "on-thy-fly" whether it shows a tooltip and which text it shows... – Yahia Apr 13 '13 at 09:05

3 Answers3

3

Just a thought, but try using messages rather than leveraging the actual mouse.

Play with WM_HOVER, WM_MOUSEHOVER, WM_MOUSEENTER

 SendMessage(_buttonCtrl.Handle, WM_MOUSEHOVER, ..., ...)

etc.

Your screenshot looks like a custom control so it's going to be a matter of hacking to figure out what triggers the tooltip.

Potentially, you can send several WM_MOUSEENTER's or WM_MOUSEHOVER's simultaneously. It really depends on the underlying code.

If this is causing too long of a delay, (and none of the suggested solutions work) think about pulling tooltip testing into a secondary test pool that is executed less frequently or only on specific request.

Also... and I'm sure you've tried it already... but if not, check out UI Spy and see if it reports any information about the tooltip before it's actually generated.

Steve Kallestad
  • 3,484
  • 2
  • 23
  • 31
  • See update 3 & 5, if we know _tooltipCtrl.Handle, all is set, and then there is no reason for this bounty. – David Apr 13 '13 at 10:02
  • @David - sending the message to the Control (i.e. the button), not the tooltip itself. – Steve Kallestad Apr 13 '13 at 10:19
  • even if we send message to the button control, the tooltip window be created and visible after some delay (default 500ms), this does not work for my case due to performance reasons. – David Apr 13 '13 at 10:24
  • 1
    @David If you can send the message to all controls you could be looking at a 500ms delay all together, not for each button. With 10 buttons, it save 4.5 seconds - significant savings. The tt text is likely to be stored as a resource, not a property (unless the original developer didn't think about internationalization). That being the case, you won't be able to call the method directly as the control has no UI automation support and your only choice is to either find and read the resource (not a good testing practice) or get the control to call it's tt method (triggered by WM_Messages). – Steve Kallestad Apr 13 '13 at 10:45
  • (comment too long, so continuing...) If you can see the tooltip text with UI Spy, you can grab it quickly. Otherwise, you have to send messages and wait or move the mouse, wait for the mouse to send messages, and wait. – Steve Kallestad Apr 13 '13 at 10:48
  • this is almost infeasible, although I have not tested that. Using your approach, it might mean that at the same time, all tooltips windows are visible, I have not ever seen such possibilities. Did you have a success using SendMessage() simutaneously to a collection of controls and make all the buttons' tooltips visible at almost the same time? Anyway, your comment is very interesting! – David Apr 13 '13 at 10:50
  • Response to your continued comment: the tooltip ctrl cannot be grabbed with UISpy, it is dynamically generated, and as a child window of desktop, after we placing the mouse over the button. – David Apr 13 '13 at 10:54
  • You can do all kinds of funky things sending messages directly to applications. From a programmers perspective, you show a window on message receive, destroy the window on another message receipt (wm_mouse_exit or something similar). Maintaining a collective show/hide tooltip function for an entire toolbar does happen, so this kind of a thing won't always work, but if they are individual controls, they won't care about what else is happening. The OS will determine top/bottom/overlap and you'll be able to access each to determine size, content, etc. regardless of visibility. – Steve Kallestad Apr 13 '13 at 10:59
  • "The OS will determine top/bottom/overlap and you'll be able to access each to determine size, content, etc. regardless of visibility", this might be untrue. According to my spy with Inspect.exe, the tooltips handle is available only after it is visible. – David Apr 13 '13 at 11:02
  • yeah, but the controls themselves contain the function that opens the window. each control creates a new child window to create the tooltip and maintains a handle to eventually destroy it. All your doing by sending the messages to the control directly is triggering the function call to create the child window. It's the same thing that the mouse is doing, you're just doing it directly. There's no OS rule that says "only one tooltip may be visible at one time". – Steve Kallestad Apr 13 '13 at 11:09
  • There is the possibility that the controls themselves don't hold that function and the toolbar does. In that case, sending multiple messages to different controls might cause a race condition and generate an error. You have to try it and see. – Steve Kallestad Apr 13 '13 at 11:10
  • 1
    Quick follow up... I just did a little reading, and WM_MOUSEHOVER might be exactly what you need. The OS only sends this message AFTER the specified timeout. If the control is triggered on WM_MOUSEHOVER, sending that message will get the tooltip to appear immediately. If it's on WM_MOUSEENTER or WM_MOUSEMOVE you'll be relegated to waiting. WM_MOUSEHOVER is paired with WM_MOUSELEAVE. WM_MOUSEHOVER has been around since windows 2000. – Steve Kallestad Apr 13 '13 at 11:24
  • Any reference links? Thanks! – David Apr 13 '13 at 11:25
  • http://msdn.microsoft.com/en-us/library/windows/desktop/ms645613%28v=vs.85%29.aspx – Steve Kallestad Apr 13 '13 at 11:28
  • Your parameters should just be the X,Y coordinates of where the mouse should be (so somewhere within the square that the control is). It should be really easy to give it a shot. – Steve Kallestad Apr 13 '13 at 11:31
  • Trying now, shall let you know if it works! Excellent tips! Thanks! – David Apr 13 '13 at 11:33
  • fingers crossed. I'm in the midst of building an automation framework myself right now (not windows based), I know the frustrations. – Steve Kallestad Apr 13 '13 at 11:35
  • TurtlePowered, just tested your idea, but it does not work. As stated in the MSDN link, the call SendMessage(..., WM_MOUSEHOVER,...) posted to a window when the cursor hovers over the client area of the window for the period of time specified in a prior call to TrackMouseEvent. How long does this hover lasts? It is speificed in the "TrackMouseEvent" function with a struct parameter. I tried several time intervals, but none of them activated the tooltip window. – David Apr 13 '13 at 13:11
  • What if you set system time? Sounds ugly, but if you can, why not? :-) I will have to solve the very same problem you were working on - except that for me performance is not that big issue, so thank you for the overall sum up of the question. – Kobor42 Oct 01 '13 at 12:07
1

The best thing from UI Automation perspective we can do is to subscribe to the ToolTip Opened Event and in the event handler process the same. This link has a sample http://msdn.microsoft.com/en-us/library/ms752286.aspx . List of UI Automation Events can be found here http://msdn.microsoft.com/en-us/library/ms748252.aspx . Details on AutomationElement.ToolTipOpenedEvent can be found here http://msdn.microsoft.com/en-us/library/system.windows.automation.automationelement.tooltipopenedevent.aspx .

With UI Automation at the least we should let the things to happen, either button click or opening a window or displaying a tooltip. So subscribing to ToolTip events would do the job here with better performance than hard Sleep() delays. Otherwise the hacky way(although not end to end) as mentioned by few, is to get the resource string ids for the tooltip string upfront and verify the resource strings during execution of automated tests.

Rahul Sundar
  • 480
  • 8
  • 26
  • Unfortunately, the "ToolTipOpenedEvent" is raised only when the tooltip is visible or "opened", how do we programmaticly make this tooltip "open"? If it relies on moving mouse over the tool (i.e. the button) window, then this won't work due to performance requirement. – David Apr 13 '13 at 10:35
  • We can simulate Mouse Hover events on these buttons, which will invoke the tooltip. This would be bit faster than moving\hovering the mouse directly. Again each element is associated with an action. Click to invoke button, hover for invoking tooltip and these associations can't be changed. – Rahul Sundar Apr 13 '13 at 12:51
0

Subclass the parent of the tooltip, i.e. replace the button's windproc by yours and filter the WM_NOTIFY for TTN_GETDISPINFO case, maybe like so..

case TTN_GETDISPINFO:
{
 // do this first
 NMTTDISPINFO pttdi = (LPNMTTDISPINFO)lparam;

 // next let the system do the default, ie fill the 
 //   relevant structures with the 
 // text that will appear on the tooltip. 
 // so

 CallWindowProc(OldListViewProc, hwnd, message, wparam, lparam);
 
 // then the text you seek will be in the 
 // NMTTDISPINFO pttdi structure. You can even alter 
 // the text to suit your needs if you want.     
}

Apparently the MSDN Library for Visual Studio 2008 shows that for _WIN32_WINNT >= 0x0600, i.e. after Vista, there is an extra member of this structure HBITMAP hbmp that is kind of hidden but stands for "A handle to the large preview bitmap to be shown in the tooltip".

khalfan
  • 23
  • 6