January 10th, 2020

Over-documenting TTM_RELAYEVENT and why it results in a one-second periodic timer running as long as the tooltip is visible

If you create a Windows classic Win32 tooltip control, you get to specify whether you want the control to subclass the window with which is associated, or whether you promise to forward mouse messages with the TTM_RELAY­EVENT message. The tooltip control uses these mouse messages to help decide when the tooltip should be displayed and hidden.

If you agree to forward the messages yourself, then you may find that the tooltip control runs a one-second periodic timer for as long as the tooltip is visible. What’s that for?

The timer is there so the tooltip can detect when the mouse has left the associated window. When that happens, the tooltip hides itself because the mouse is no longer on the tooltip target.

Why does it need a timer to do this?

Rewind to 1994. The tooltip control is being developed, and the TTM_RELAY­EVENT message gives the tooltip control insight into what the mouse is doing in the associated window. It uses this information to detect that the mouse has dwelled inside the tooltip target for the required amount of time, which causes the tooltip to appear. It also uses this information to detect that the mouse has moved to another part of the associated window that is not part of the tooltip target, at which point it can remove the tooltip.

But there’s another case that isn’t covered by this: The tooltip needs to know when the mouse has left the tooltip target due to the mouse leaving the associated window entirely.

Since mouse messages are delivered to the window under the mouse cursor,¹ moving the mouse out of the associated window entirely means that the associated window has nothing to forward to the control via the TTM_RELAY­EVENT message The only way for the tooltip to know that the mouse has left the window entirely is for it to run a timer and poll the mouse position.

That’s how things were in Windows 95.

The documentation for the TTM_RELAY­EVENT message says

A tooltip control processes only the following messages passed to it by the TTM_RELAY­EVENT message:

  • WM_LBUTTON­DOWN
  • WM_LBUTTON­UP
  • WM_MBUTTON­DOWN
  • WM_MBUTTON­UP
  • WM_MOUSE­MOVE
  • WM_RBUTTON­DOWN
  • WM_RBUTTON­UP

All other messages are ignored.

Move forward to 1998. The Track­Mouse­Event function was added. Among other things, this allows a window to be notified when the mouse leaves the window outright. Great, the tooltip control can take advantage of this so that it doesn’t need to poll the mouse to find out whether it left the window. It can just wait for the WM_MOUSE­LEAVE message.

Except that it can’t.

Because the TTM_RELAY­EVENT message already had documentation that said “All other messages are ignored.” Programs were written based on the fact that only the messages given in the documentation need to be forwarded to the tooltip control. If they got any other message, they “optimized” their code by not bothering to forward it to the tooltip control.

This meant that the tooltip control would never get the WM_MOUSE­LEAVE message, since the documentation told people that the tooltip control ignored the message.

So despite the availability of an efficient and battery-friendly way of detecting whether the mouse has left a window, the tooltip control cannot use it because the documentation revealed too much information, and people came to rely on that extra information.

If the documentation had merely said, “Forward all messages between WM_MOUSE­FIRST and WM_MOUSE­LAST to the tooltip control,” without enumerating which mouse messages the tooltip control actually cares about, then it would have been possible to use the efficient version, because everybody would be forwarding all mouse messages, which includes the new WM_MOUSE­LEAVE message.

So things are bad because we wrote too much documentation. The documentation described the implementation rather than the contract.

All is not lost, however.

If you set the TTF_SUBCLASS flag when you create a tooltip target, then you are telling the tooltip control to subclass the window in order to grab the mouse messages. In this case, you don’t need to (and shouldn’t) use the TTM_RELAY­EVENT message. And if the tooltip control is subclassing the window, it can see all the messages, and that includes the WM_MOUSE­LEAVE message.

So use the TTF_SUBCLASS flag when you create your tooltip targets. Your tooltip will respond more promptly to the user moving out of the window, and you won’t burn up the user’s battery.

¹ Assuming that mouse capture is not in effect. Tooltips do not capture the mouse, because that would prevent the user from using the mouse to do normal mouse things.

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

16 comments

Discussion is closed. Login to edit/delete existing comments.

  • Kessler, Heinz

    As I always say: “Timers are the developer’s duck tape”

  • Jonathan Potter

    Heh, I always assumed TrackMouseEvent was implemented internally using a timer.

  • Neil Rashbrook

    (The atom feeds seems to be broken again. But hey, it at least it made it to this decade… looks like the RSS feed last updated on New Year’s Eve…)

    Edit: Fixed again, thanks!

  • David Streeter

    Oh no, it’s too late to suggest a ‘British Monarchy Succession” tree-traversal article 🙁

    • Raymond ChenMicrosoft employee Author

      I think that’s the same as preorder, isn’t it?

      • Alex Martin

        How do you evaluate the gender of a tree node?

      • David Streeter

        Same way you distinguish a mail plane 🙂

  • cheong00

    Maybe the tooltip control can offer optimization that as soon as it see a WM_MOUSELEAVE message got forwarded, it can silently disable the timer it created.

    So even if the control isn’t subclassed, you can avoid burning user battery after (maybe) a few minutes. (And add a new tips in category of “weird Windows workaround with good reason”.)

  • skSdnW

    CCM_SETVERSION could be used to allow different behavior for new comctl32 versions, that is why this message exists. Comctl v6 was also a new opportunity to change behaviors.

    • Raymond ChenMicrosoft employee Author

      “Hey, I think four years ago, there was this thing we wanted to do but couldn’t. Do you remember what that was?” (This assumes that there’s even a person from the old team that is still on the new team.)

      • Alex Martin

        Shouldn’t it have been documented in a bug database somewhere?

      • Raymond ChenMicrosoft employee Author

        “Hey, go look in our 24-million-entry database to see if anything applies here.”

      • Alex Martin

        Ah, that would be the problem. I had assumed the old bug databases had been migrated. I suppose that doesn’t really make sense given that Windows 98 is a completely different architecture, but I hadn’t thought about it that way.

      • Alex Martin

        I’m not saying scroll through the whole thing; that is obviously a bad plan. It’s a database. Filter on status (open or on hold or wontfix-compatibility or whatever) and component (comctl32) and you’d have a to-do list of things to consider for v6. Maybe I don’t understand the quirks of the system in question but it seems like you should be able to do something along those lines.

      • Raymond ChenMicrosoft employee Author

        That's still a lot of bugs to have to read through to see if any of them have a comment of the form "In the future, we might choose to..." And the bugs might be in databases that have been retired, so you will never find them. In this case, the "In the future, we might choose to..." would be a comment in the Windows 98 bug database, but comctl32 v6 didn't arrive until Windows...

        Read more