{"id":30703,"date":"2006-06-28T10:00:05","date_gmt":"2006-06-28T10:00:05","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/06\/28\/multiplexing-multiple-tools-into-one-in-a-tooltip\/"},"modified":"2006-06-28T10:00:05","modified_gmt":"2006-06-28T10:00:05","slug":"multiplexing-multiple-tools-into-one-in-a-tooltip","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060628-05\/?p=30703","title":{"rendered":"Multiplexing multiple tools into one in a tooltip"},"content":{"rendered":"<p>\nThe tooltip control lets you set multiple &#8220;tools&#8221;\n(regions of the owner window) for it to monitor.\nThis is very convenient when the number of tools is\nmanageably small and they don&#8217;t move around much.\nFor example, the toolbar control creates a tool for\neach button.\nBut if you have hundreds or thousands of screen elements\nwith tooltips,\ncreating a tool for each one can be quite a lot of work,\nespecially if the items move around a lot.\nFor example, the listview control does not create a\nseparate tool for each listview item,\nsince a listview can have thousands of items,\nand scrolling the view results in the items moving around.\nUpdating the tool information whenever the listview\ncontrol scrolls would be extremely slow,\nand the work would be out of proportion to the benefit.\n(Updating thousands of tools on the off chance the user\nhovers over one of them doesn&#8217;t really sit well on the\ncost\/benefit scale.)\n<\/p>\n<p>\nInstead of creating a tool for each item,\nyou can instead multiplex all the tools into one,\nupdating that one tool dynamically to be the one\ncorresponding to the element the user is currently interacting with.\nWe&#8217;ll start with\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\na fresh scratch program<\/a>\nand create a few items\nwhich we want to give tooltips for.\n<\/p>\n<pre>\n<font COLOR=\"blue\">int g_cItems = 10;\nint g_cyItem = 20;\nint g_cxItem = 200;\nBOOL\nGetItemRect(int iItem, RECT *prc)\n{\n SetRect(prc, 0, g_cyItem * iItem,\n         g_cxItem, g_cyItem * (iItem + 1));\n return iItem &gt;= 0 &amp;&amp; iItem &lt; g_cItems;\n}\nint\nItemHitTest(int x, int y)\n{\n if (x &lt; 0 || x &gt; g_cxItem) return -1;\n if (y &lt; 0 || y &gt; g_cItems * g_cyItem) return -1;\n return y \/ g_cyItem;\n}<\/font>\nvoid\nPaintContent(HWND hwnd, PAINTSTRUCT *pps)\n{\n <font COLOR=\"blue\">COLORREF clrSave = GetBkColor(pps-&gt;hdc);\n for (int iItem = 0; iItem &lt; g_cItems; iItem++) {\n  RECT rc;\n  GetItemRect(iItem, &amp;rc);\n  COLORREF clr = RGB((iItem &amp; 1) ? 0x7F : 0,\n                     (iItem &amp; 2) ? 0x7F : 0,\n                     (iItem &amp; 4) ? 0x7F : 0);\n  if (iItem &amp; 8) clr *= 2;\n  SetBkColor(pps-&gt;hdc, clr);\n  ExtTextOut(pps-&gt;hdc, rc.left, rc.top,\n             ETO_OPAQUE, &amp;rc, TEXT(\"\"), 0, NULL);\n }\n SetBkColor(pps-&gt;hdc, clrSave);<\/font>\n}\n<\/pre>\n<p>\nWe merely paint a few colored bands.\nTo make things more interesting, you can add scroll bars.\nI leave you to deal with that yourself,\nsince it would be distracting from the point here,\nalthough it would also make the sample a bit more realistic.\n<\/p>\n<p>\nNext, we create a tooltip control and instead of\ncreating a tool for each element, we create only one.\nFor starters, it&#8217;s an empty tool with no rectangle.\nThe <code>g_iItemTip<\/code> variable tells us which item\nthis tooltip is standing in for at any particular moment;\nwe use <code>-1<\/code> as a sentinel indicating that the tooltip\nis not active.\n<\/p>\n<pre>\n<font COLOR=\"blue\">HWND g_hwndTT;\nint g_iItemTip;<\/font>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n <font COLOR=\"blue\">g_hwndTT = CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, NULL,\n                           TTS_NOPREFIX,\n                           0, 0, 0, 0,\n                           hwnd, NULL, g_hinst, NULL);\n if (!g_hwndTT) return FALSE;\n g_iItemTip = -1;\n TOOLINFO ti = { sizeof(ti) };\n ti.uFlags = TTF_TRANSPARENT;\n ti.hwnd = hwnd;\n ti.uId = 0;\n ti.lpszText = TEXT(\"Placeholder tooltip\");\n SetRectEmpty(&amp;ti.rect);\n SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&amp;ti);<\/font>\n return TRUE;\n}\n<\/pre>\n<p>\nYou may have noticed that we do not use the <code>TTF_SUBCLASS<\/code>\nflag in our tool.\nWe&#8217;ll see why later.\n<\/p>\n<p>\nThe single tool for the tooltip covers our entire client rectangle.\nWe maintain this property as the window resizes.\n<\/p>\n<pre>\nvoid\nOnSize(HWND hwnd, UINT state, int cx, int cy)\n{\n <font COLOR=\"blue\">TOOLINFO ti = { sizeof(ti) };\n ti.hwnd = hwnd;\n ti.uId = 0;\n GetClientRect(hwnd, &amp;ti.rect);\n SendMessage(g_hwndTT, TTM_NEWTOOLRECT, 0, (LPARAM)&amp;ti);<\/font>\n}\n<\/pre>\n<p>\nWe need to keep the <code>g_iItemTip<\/code> up to date\nso we know which item our tooltip is standing for at any\nparticular moment.\nThat is done by the <code>UpdateTooltip<\/code> function:\n<\/p>\n<pre>\n<font COLOR=\"blue\">void\nUpdateTooltip(int x, int y)\n{\n int iItemOld = g_iItemTip;\n g_iItemTip = ItemHitTest(x, y);\n if (iItemOld != g_iItemTip) {\n   SendMessage(g_hwndTT, TTM_POP, 0, 0);\n }\n}<\/font>\n<\/pre>\n<p>\nTo update the tooltip, we check\nwhether the mouse is over the same item as it was last time.\nIf not, then we update our &#8220;Which item is under the mouse now?&#8221;\nvariable and pop the old bubble (if any).\nAnd we always relay the message to the tooltip so it can do its\ntooltip thing.\nThis function also explains why we did not use the\n<code>TTF_SUBCLASS<\/code> flag when we created our tool:\nWe need to do some processing before the tooltip.\nIf we had allowed the tooltip to subclass, then it would\nprocess the mouse message first,\nwhich means that our <code>TTM_POP<\/code>  would have popped\nthe new updated tooltip instead of the stale\nold tooltip.\n<\/p>\n<p>\nThis <code>UpdateTooltip<\/code> function is very important.\nIt must be called any time the mouse may be hovering over\na different item.\nThis could be because the mouse moved or because the items\nunder the mouse changed positions.\nI don&#8217;t have any scrolling in this example, but if I did,\nthen you would see a call to\n<code>UpdateTooltip<\/code>\nwhenever we updated the scroll origin point\nbecause the act of scrolling may have moved the item\nthat was under the mouse.\n(Failing to maintain mouse state after a scrolling operation\nis a common programming oversight.)\nFurthermore, if items were added or deleted dynamically,\nthen a call to <code>UpdateTooltip<\/code> would have\nto be made once an item was added or deleted\nbecause the added or deleted item might be the one\nunder the mouse.\n<\/p>\n<p>\nThe easy one to take care of is the mouse motion:\n<\/p>\n<pre>\n<font COLOR=\"blue\">void\nRelayEvent(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n UpdateTooltip(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));\n MSG msg;\n msg.hwnd = hwnd;\n msg.message = uiMsg;\n msg.wParam = wParam;\n msg.lParam = lParam;\n SendMessage(g_hwndTT, TTM_RELAYEVENT, 0, (LPARAM)&amp;msg);\n}<\/font>\nLRESULT CALLBACK\nWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n <font COLOR=\"blue\">if ((uiMsg &gt;= WM_MOUSEFIRST &amp;&amp; uiMsg &lt;= WM_MOUSELAST) ||\n     uiMsg == WM_NCMOUSEMOVE) {\n  RelayEvent(hwnd, uiMsg, wParam, lParam);\n }<\/font>\n switch (uiMsg) {\n  ... as before ...\n}\n<\/pre>\n<p>\nIf we get a mouse message, then the\n<code>RelayEvent<\/code> message updates our tooltip state\nand then relays the message to the tooltip.\nSee the discussion above for the importance of doing this\nin the right order.\n<\/p>\n<p>\nYou can run the program now.\nObserve that the program acts as if each colored band has\nits own tooltip,\neven though there is really only one tooltip that we\nkeep recycling.\n<\/p>\n<p>\nWe&#8217;re still not done.\nThe tooltip text is the same for each item,\nwhich is unrealistic for a real program.\nWe&#8217;ll address this next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The tooltip control lets you set multiple &#8220;tools&#8221; (regions of the owner window) for it to monitor. This is very convenient when the number of tools is manageably small and they don&#8217;t move around much. For example, the toolbar control creates a tool for each button. But if you have hundreds or thousands of screen [&hellip;]<\/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-30703","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The tooltip control lets you set multiple &#8220;tools&#8221; (regions of the owner window) for it to monitor. This is very convenient when the number of tools is manageably small and they don&#8217;t move around much. For example, the toolbar control creates a tool for each button. But if you have hundreds or thousands of screen [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30703","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=30703"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30703\/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=30703"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=30703"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=30703"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}