{"id":30693,"date":"2006-06-29T10:00:00","date_gmt":"2006-06-29T10:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/06\/29\/generating-tooltip-text-dynamically\/"},"modified":"2006-06-29T10:00:00","modified_gmt":"2006-06-29T10:00:00","slug":"generating-tooltip-text-dynamically","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060629-00\/?p=30693","title":{"rendered":"Generating tooltip text dynamically"},"content":{"rendered":"<p>\nOur multiplexed tooltip right now is displaying the same\nstring for all items.\nLet&#8217;s make it display something a bit more interesting\nso it&#8217;s more obvious that what we&#8217;re doing is actually working.\n<\/p>\n<pre>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n ...\n <font COLOR=\"blue\"><strike>\/\/ ti.lpszText = TEXT(\"Placeholder tooltip\");<\/strike>\n ti.lpszText = LPSTR_TEXTCALLBACK;<\/font>\n ...\n}\n<font COLOR=\"blue\">LRESULT\nOnNotify(HWND hwnd, int idFrom, NMHDR *pnm)\n{\n if (pnm-&gt;hwndFrom == g_hwndTT) {\n  switch (pnm-&gt;code) {\n  case TTN_GETDISPINFO:\n   {\n    NMTTDISPINFO *pdi = (NMTTDISPINFO *)pnm;\n    if (g_iItemTip &gt;= 0) {\n     \/\/ szText is 80 characters, so %d will fit\n     wsprintf(pdi-&gt;szText, TEXT(\"%d\"), g_iItemTip);\n    } else {\n     pdi-&gt;szText[0] = TEXT('\\0');\n    }\n    pdi-&gt;lpszText = pdi-&gt;szText;\n   }\n   break;\n  }\n }\n return 0;\n}<\/font>\n\/\/ Add to WndProc\n<font COLOR=\"blue\"> HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);<\/font>\n<\/pre>\n<p>\nInstead of providing fixed tooltip text,\nwe generate it on the fly by setting the text to\n<code>LPSTR_TEXTCALLBACK<\/code>\nand producing the text in response to the\n<code>TTN_GETDISPINFO<\/code> notification.\nThe technique of generating tooltip text dynamically is\nuseful in scenarios other than this.\nFor example, the tooltip text may change based\non some state that changes often\n(&#8220;Back to &lt;insert name of previous page&gt;&#8221;)\nor the tooltip text may be slow or expensive to compute\n(&#8220;Number of pages: 25&#8221;).\nIn both cases, updating the tooltip text lazily is the\ncorrect thing to do,\nsince it falls into the &#8220;pay for play&#8221; model:\nOnly if the user asks for a tooltip does the program go to\nthe extra effort of producing one.\n<\/p>\n<p>\nNow that you&#8217;ve played with the program a bit,\nlet&#8217;s tweak it every so slightly to illustrate a point I made\nlast time:\nWe&#8217;ll make the <code>+<\/code>\nand <code>-<\/code> keys add and remove colored bars.\nThis lets you see how the tooltip code updates itself\nwhen items move around.\n<\/p>\n<pre>\n<font COLOR=\"blue\">void\nInvalidateItems(HWND hwnd, int iItemMin, int iItemMax)\n{\n RECT rc;\n SetRect(&amp;rc, 0, g_cyItem * iItemMin,\n         g_cxItem, g_cyItem * iItemMax);\n InvalidateRect(hwnd, &amp;rc, TRUE);\n}\nvoid\nUpdateTooltipFromMessagePos(HWND hwnd)\n{\n DWORD dwPos = GetMessagePos();\n POINT pt = { GET_X_LPARAM(dwPos),\n              GET_Y_LPARAM(dwPos) };\n ScreenToClient(hwnd, &amp;pt);\n UpdateTooltip(pt.x, pt.y);\n}\nvoid\nOnChar(HWND hwnd, TCHAR ch, int cRepeat)\n{\n switch (ch) {\n case TEXT('+'):\n  g_cItems += cRepeat;\n  InvalidateItems(hwnd, g_cItems - cRepeat, g_cItems);\n  UpdateTooltipFromMessagePos(hwnd);\n  break;\n case TEXT('-'):\n  if (cRepeat &gt; g_cItems) cRepeat = g_cItems;\n  g_cItems -= cRepeat;\n  InvalidateItems(hwnd, g_cItems, g_cItems + cRepeat);\n  UpdateTooltipFromMessagePos(hwnd);\n  break;\n }\n}<\/font>\n\/\/ Add to WndProc\n<font COLOR=\"blue\"> HANDLE_MSG(hwnd, WM_CHAR, OnChar);<\/font>\n<\/pre>\n<p>\nWe have a few new helper functions.\nThe first invalidates the rectangle associated with a\nrange of items.\n(Conforming to Hungarian convention, the term &#8220;Max&#8221;\nrefers to the first element outside the range.\nIn other words, &#8220;Min\/Max&#8221; is\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/02\/18\/75652.aspx\">\nendpoint-exclusive<\/a>.)\nControls that manage sub-elements will almost always have\na function like\n<code>InvalidateItems<\/code>\nin order to trigger a repaint when a\nsub-element changes its visual appearance.\n<\/p>\n<p>\nThe next helper function is <code>UpdateTooltipFromMessagePos<\/code>\nwhich pretty much does what it says:\nIt takes the message position and passes those coordinates\n(suitably converted) to <code>UpdateTooltip<\/code> in order\nto keep everything in sync.\nFinally, the <code>WM_CHAR<\/code> handler adds or removes items\nbased on what the user typed (taking autorepeat into account).\nWhenever we change the number of items, we update the tooltip\nbecause one of the items that was added or removed may have been\nthe one beneath the cursor.\n<\/p>\n<p>\nThere is an important subtlety to the\n<code>UpdateTooltipFromMessage<\/code> function:\nRemember that the message position retrieved via\n<code>GetMessagePos<\/code> applies to the most recent\nmessage retrieved from the message queue.\nMessages delivered via <code>SendMessage<\/code>\nbypass the message queue and therefore do not update\nthe queue message position.\nOnce again, we see by a different means that\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2005\/05\/30\/423202.aspx\">\nyou can&#8217;t simulate input with <code>SendMessage<\/code><\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Our multiplexed tooltip right now is displaying the same string for all items. Let&#8217;s make it display something a bit more interesting so it&#8217;s more obvious that what we&#8217;re doing is actually working. BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { &#8230; \/\/ ti.lpszText = TEXT(&#8220;Placeholder tooltip&#8221;); ti.lpszText = LPSTR_TEXTCALLBACK; &#8230; } LRESULT OnNotify(HWND hwnd, int idFrom, [&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-30693","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Our multiplexed tooltip right now is displaying the same string for all items. Let&#8217;s make it display something a bit more interesting so it&#8217;s more obvious that what we&#8217;re doing is actually working. BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { &#8230; \/\/ ti.lpszText = TEXT(&#8220;Placeholder tooltip&#8221;); ti.lpszText = LPSTR_TEXTCALLBACK; &#8230; } LRESULT OnNotify(HWND hwnd, int idFrom, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30693","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=30693"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30693\/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=30693"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=30693"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=30693"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}