{"id":103316,"date":"2020-01-10T07:00:00","date_gmt":"2020-01-10T15:00:00","guid":{"rendered":"http:\/\/devblogs.microsoft.com\/oldnewthing\/?p=103316"},"modified":"2020-01-09T18:15:26","modified_gmt":"2020-01-10T02:15:26","slug":"20200110-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20200110-00\/?p=103316","title":{"rendered":"Over-documenting TTM_RELAYEVENT and why it results in a one-second periodic timer running as long as the tooltip is visible"},"content":{"rendered":"<p>If you create a Windows classic Win32 <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/controls\/tooltip-controls\"> tooltip control<\/a>, 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 <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows\/win32\/controls\/ttm-relayevent\"> the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> message<\/a>. The tooltip control uses these mouse messages to help decide when the tooltip should be displayed and hidden.<\/p>\n<p>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&#8217;s that for?<\/p>\n<p>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.<\/p>\n<p>Why does it need a timer to do this?<\/p>\n<p>Rewind to 1994. The tooltip control is being developed, and the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> 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.<\/p>\n<p>But there&#8217;s another case that isn&#8217;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.<\/p>\n<p>Since mouse messages are delivered to the window under the mouse cursor,\u00b9 moving the mouse out of the associated window entirely means that the associated window has nothing to forward to the control via the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> 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.<\/p>\n<p>That&#8217;s how things were in Windows 95.<\/p>\n<p>The documentation for the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> message says<\/p>\n<blockquote class=\"q\">\n<p>A tooltip control processes only the following messages passed to it by the <b>TTM_<code><\/code>RELAY\u00adEVENT<\/b> message:<\/p>\n<ul>\n<li>WM_<code><\/code>LBUTTON\u00adDOWN<\/li>\n<li>WM_<code><\/code>LBUTTON\u00adUP<\/li>\n<li>WM_<code><\/code>MBUTTON\u00adDOWN<\/li>\n<li>WM_<code><\/code>MBUTTON\u00adUP<\/li>\n<li>WM_<code><\/code>MOUSE\u00adMOVE<\/li>\n<li>WM_<code><\/code>RBUTTON\u00adDOWN<\/li>\n<li>WM_<code><\/code>RBUTTON\u00adUP<\/li>\n<\/ul>\n<p>All other messages are ignored.<\/p>\n<\/blockquote>\n<p>Move forward to 1998. The <code>Track\u00adMouse\u00adEvent<\/code> 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&#8217;t need to poll the mouse to find out whether it left the window. It can just wait for the <code>WM_<code><\/code>MOUSE\u00adLEAVE<\/code> message.<\/p>\n<p>Except that it can&#8217;t.<\/p>\n<p>Because the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> message already had documentation that said &#8220;All other messages are ignored.&#8221; 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 &#8220;optimized&#8221; their code by not bothering to forward it to the tooltip control.<\/p>\n<p>This meant that the tooltip control would never get the <code>WM_<code><\/code>MOUSE\u00adLEAVE<\/code> message, since the documentation told people that the tooltip control ignored the message.<\/p>\n<p>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.<\/p>\n<p>If the documentation had merely said, &#8220;Forward all messages between <code>WM_<code><\/code>MOUSE\u00adFIRST<\/code> and <code>WM_<code><\/code>MOUSE\u00adLAST<\/code> to the tooltip control,&#8221; 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 <code>WM_<code><\/code>MOUSE\u00adLEAVE<\/code> message.<\/p>\n<p>So things are bad because we wrote too much documentation. The documentation described the implementation rather than the contract.<\/p>\n<p>All is not lost, however.<\/p>\n<p>If you set the <code>TTF_<code><\/code>SUBCLASS<\/code> 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&#8217;t need to (and shouldn&#8217;t) use the <code>TTM_<code><\/code>RELAY\u00adEVENT<\/code> message. And if the tooltip control is subclassing the window, it can see <i>all<\/i> the messages, and that includes the <code>WM_<code><\/code>MOUSE\u00adLEAVE<\/code> message.<\/p>\n<p>So use the <code>TTF_<code><\/code>SUBCLASS<\/code> flag when you create your tooltip targets. Your tooltip will respond more promptly to the user moving out of the window, and you won&#8217;t burn up the user&#8217;s battery.<\/p>\n<p>\u00b9 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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Too much information leads to trouble.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-103316","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Too much information leads to trouble.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103316","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=103316"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/103316\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=103316"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=103316"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=103316"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}