{"id":28713,"date":"2006-12-14T10:00:02","date_gmt":"2006-12-14T10:00:02","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/12\/14\/computing-listview-infotips-in-the-background\/"},"modified":"2006-12-14T10:00:02","modified_gmt":"2006-12-14T10:00:02","slug":"computing-listview-infotips-in-the-background","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20061214-02\/?p=28713","title":{"rendered":"Computing listview infotips in the background"},"content":{"rendered":"<p>\nWhen the listview control asks you for an infotip, it sends you\nthen <code>LVN_GETINFOTIP<\/code> notification, and when you return,\nthe result is displayed as the infotip.\nBut what if computing the infotip takes a long time?\nYou don&#8217;t want to stall the UI thread on a long operation,\nafter all.\nThis is where <code>LVM_SETINFOTIP<\/code> comes in.\n<\/p>\n<p>\nIf you want to say, &#8220;Um, I&#8217;m not ready with that infotip yet,&#8221;\nyou do two things:\nFirst, you return a blank infotip to the listview in response\nto <code>LVN_GETINFOTIP<\/code>;\nthis tells the listview not to display anything.\nThen, when you have the infotip, you send the\n<code>LVM_SETINFOTIP<\/code> message to say,\n&#8220;Oh, here&#8217;s that infotip you asked for.\nIf you were still wondering.&#8221;\nIf the user is still hovering over the item that the infotip was\nrequested for, the infotip will be displayed.\nOtherwise, the infotip will be thrown away (since the user doesn&#8217;t\nwant to see it any more).\n<\/p>\n<p>\nHere&#8217;s a quick and dirty (and not very good) implementation of\nthis algorithm, just to illustrate the point.\nStart with the program from last time and make the following\nchanges:\n<\/p>\n<pre>\n\/\/ add to top of file\n<font COLOR=\"blue\">#define UNICODE\n#define _UNICODE<\/font>\n<font COLOR=\"blue\">class BackgroundInfoTip {\npublic:\n BackgroundInfoTip() { ZeroMemory(&amp;m_sit, sizeof(m_sit)); }\n BOOL Start(HWND hwnd, NMLVGETINFOTIP *pit)\n {\n  m_hwnd = hwnd;\n  m_sit.cbSize = sizeof(m_sit);\n  m_sit.dwFlags = 0;\n  m_sit.iItem = pit-&gt;iItem;\n  m_sit.iSubItem = pit-&gt;iSubItem;\n  if ((pit-&gt;dwFlags &amp; LVGIT_UNFOLDED) ||\n     (m_pszPrefix = StrDup(pit-&gt;pszText)) != NULL) {\n   return QueueUserWorkItem(s_Work, this, WT_EXECUTELONGFUNCTION);\n  }\n  return FALSE;\n }\n ~BackgroundInfoTip() {\n  LocalFree(m_pszPrefix);\n  CoTaskMemFree(m_sit.pszText);\n }\n static DWORD CALLBACK s_Work(void *lpParameter);\n void Work();\n HWND m_hwnd;\n LVSETINFOTIP m_sit;\n LPTSTR m_pszPrefix;\n};\nvoid BackgroundInfoTip::Work()\n{\n Sleep(3000); \/\/ artificial delay\n TCHAR szInfotip[INFOTIPSIZE];\n if (m_pszPrefix) {\n  StringCchCopy(szInfotip, INFOTIPSIZE, m_pszPrefix);\n  StringCchCat(szInfotip, INFOTIPSIZE, TEXT(\"\\r\\n\"));\n } else {\n  szInfotip[0] = TEXT('\\0');\n }\n StringCchCat(szInfotip, INFOTIPSIZE, TEXT(\"Here is an infotip\"));\n if (SUCCEEDED(SHStrDup(szInfotip, &amp;m_sit.pszText)) &amp;&amp;\n     PostMessage(m_hwnd, WM_APP, 0, (LPARAM)this)) {\n  \/\/ ownership transferred to main window\n } else {\n  delete this;\n }\n}\nDWORD BackgroundInfoTip::s_Work(void *lpParameter)\n{\n BackgroundInfoTip *self =\n    reinterpret_cast&lt;BackgroundInfoTip*&gt;(lpParameter);\n self-&gt;Work();\n return 0;\n}<\/font>\nvoid OnGetInfoTip(HWND hwnd, NMLVGETINFOTIP *pit)\n{\n if (!pit-&gt;cchTextMax) return;\n <font COLOR=\"blue\">\/\/ note: uses no-throwing \"new\"\n BackgroundInfoTip *pbit = new BackgroundInfoTip();\n if (pbit &amp;&amp; pbit-&gt;Start(hwnd, pit)) {\n  pit-&gt;pszText[0] = TEXT('\\0'); \/\/ no tip yet\n } else {\n  delete pbit;\n }<\/font>\n}\n<font COLOR=\"blue\">void FinishInfoTip(BackgroundInfoTip *pbit)\n{\n SendMessage(g_hwndChild, LVM_SETINFOTIP, 0, (LPARAM)&amp;pbit-&gt;m_sit);\n delete pbit;\n}\n    case WM_APP: FinishInfoTip((BackgroundInfoTip *)lParam); return 0;<\/font>\n<\/pre>\n<p>\nWe start by defining\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">\n<code>UNICODE<\/code> and <code>_UNICODE<\/code><\/a>\nbecause we&#8217;re using the Windows&nbsp;XP common controls (version&nbsp;6),\nand that version of the common controls supports only Unicode.\n(Version&nbsp;5 of the common controls doesn&#8217;t support the\n<code>LVM_SETINFOTIP<\/code> message.)\n<\/p>\n<p>\nNext, let&#8217;s skip ahead to the <code>OnGetInfoTip<\/code> function.\nWhen we are asked to produce an infotip, we create an instance of\nour helper class and get it started.\nOnce we&#8217;re convinced that the infotip computation is under way,\nwe return a blank infotip to the listview to tell it,\n&#8220;Don&#8217;t display anything yet.&#8221;\n<\/p>\n<p>\nThe helper class <code>BackgroundInfoTip<\/code>\nstarts by capturing the parameters of the\n<code>NMLVGETINFOTIP<\/code>.\nAgain, we pay close attention to the <code>LVGIT_UNFOLDED<\/code>\nflag:\nIf it is not set, then we save the text currently in the infotip\nso we can prepend it to the infotip text.\nWe then toss the item onto the thread pool and wait for the\nwork item to fire.\n<\/p>\n<p>\nAs before, our infotip computation is artificially simple:\nIt&#8217;s just a hard-coded string.\nIn real life you presumably would actually sit down and\ncompute something.\nI stuck in a <code>Sleep(3000)<\/code> to create an artificial\ndelay in order to simulate this &#8220;computation time&#8221;.\nOnce we have our answer,\nremembering to\nprefix the original infotip text if the item was folded,\nwe save it in the <code>LVSETINFOTIP<\/code>\nstructure and post a message back to our main thread to say,\n&#8220;Okay, the infotip is ready.&#8221;\n<\/p>\n<p>\nOn receipt of the <code>WM_APP<\/code> message\n(in a proper program, it would have a more meaningful name\nlike <code>WM_INFOTIPREADY<\/code>), we tell the listview\nthat we have our infotip, in case it was still interested.\nAnd since this completes the background infotip calculation,\nwe can delete the helper object.\n<\/p>\n<p>\nThis is not very good code because it fails to handle some\nobvious cases:\nIf the user moves to a new listview item, the listview will\nask for a new infotip.\nOur code doesn&#8217;t attempt to cancel the previous background infotip;\nas a result, if the user waves the mouse over the listview,\nwe may end up with a large number of background infotip computations,\nall but one of which will be discarded.\nEven worse, all the discarded ones will be ahead of the\nimportant one in the work item queue:\nYou&#8217;re spending all your time doing something whose result\nis going to be thrown away, and not executing\nthe work item whose result is actually useful.\n<\/p>\n<p>\nThe code also doesn&#8217;t handle the case where the window is\nclosed while the background work items are still running.\nClosing the window should cancel the work items or at least\ntell them that they don&#8217;t have a main window to talk to any more.\n<\/p>\n<p>\nAdding code to handle all these edge cases would have distracted\nfrom the point of this article, so I leave you to make this code\nmore solid as an exercise.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When the listview control asks you for an infotip, it sends you then LVN_GETINFOTIP notification, and when you return, the result is displayed as the infotip. But what if computing the infotip takes a long time? You don&#8217;t want to stall the UI thread on a long operation, after all. This is where LVM_SETINFOTIP comes [&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-28713","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>When the listview control asks you for an infotip, it sends you then LVN_GETINFOTIP notification, and when you return, the result is displayed as the infotip. But what if computing the infotip takes a long time? You don&#8217;t want to stall the UI thread on a long operation, after all. This is where LVM_SETINFOTIP comes [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/28713","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=28713"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/28713\/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=28713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=28713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=28713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}