{"id":37723,"date":"2004-09-28T07:00:00","date_gmt":"2004-09-28T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2004\/09\/28\/how-to-host-an-icontextmenu-part-6-displaying-menu-help\/"},"modified":"2004-09-28T07:00:00","modified_gmt":"2004-09-28T07:00:00","slug":"how-to-host-an-icontextmenu-part-6-displaying-menu-help","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20040928-00\/?p=37723","title":{"rendered":"How to host an IContextMenu, part 6 &#8211; Displaying menu help"},"content":{"rendered":"<p><P>\nOne of the subtleties of context menus is showing help in the\nstatus bar.  Now, the program we&#8217;ve been developing doesn&#8217;t\nhave a status bar, so we&#8217;ll fake it by putting the help text\nin the title bar.\n<\/P>\n<P>\nThe key method for this task is\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/en-us\/shellcc\/platform\/shell\/reference\/ifaces\/icontextmenu\/getcommandstring.asp\">\nIContextMenu::GetCommandString<\/A>, which\nallows communication with a context menu handler about the\nverbs in the menu.\nWe&#8217;ll have to stash yet another interface in our &#8220;instance variables\ndisguised as globals&#8221;.\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">IContextMenu *g_pcm;<\/FONT>\n<\/PRE>\n<P>\n(Remember, in a &#8220;real program&#8221;, these would be per-window instance\nvariables, not globals.)\n<\/P>\n<P>\nWe also need to update that variable during menu tracking.\n<\/P>\n<PRE>\n      <FONT COLOR=\"blue\">g_pcm = pcm;<\/FONT>\n      int iCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD, pt.x, pt.y, hwnd, NULL);\n      <FONT COLOR=\"blue\">g_pcm = NULL;<\/FONT>\n<\/PRE>\n<P>\nWith that out of the way, we can now provide feedback as the\nuser browses the popup menu.\n<\/P>\n<P>\n[Introduction of <CODE>g_pcm<\/CODE> variable added 29 September.]\n<\/P>\n<PRE>\n\/\/ This code is buggy &#8211; see below.\n<FONT COLOR=\"blue\">void OnMenuSelect(HWND hwnd, HMENU hmenu,\n                  int item, HMENU hmenuPopup, UINT flags)\n{\n  if (g_pcm &amp;&amp; item &gt;= SCRATCH_QCM_FIRST &amp;&amp;\n      item &lt;= SCRATCH_QCM_LAST) {\n    TCHAR szBuf[MAX_PATH];\n    if (FAILED(g_pcm-&gt;GetCommandString(item &#8211; SCRATCH_QCM_FIRST,\n                                       GCS_HELPTEXT, NULL,\n                                       (LPSTR)szBuf, MAX_PATH))) {\n      lstrcpyn(szBuf, TEXT(&#8220;No help available.&#8221;), MAX_PATH);\n    }\n    SetWindowText(hwnd, szBuf);\n  }\n}<\/FONT>\n<\/PRE>\n<P>\nThis function checks whether the menu selection is in\nthe range of items that we allowed the context menu to own.\nIf so, we ask for the help string (or use fallback text if\nthe context menu handler didn&#8217;t provide a help string) and\ndisplay it as our window title.\n<\/P>\n<P>\nFinally, we insert this function into our window procedure.\nWe want to update the menu selection status even if the\ncontext menu handlers do something with it, so we need to\ncall OnMenuSelect before dispatching to the context menu handlers.\n<\/P>\n<PRE>\nLRESULT CALLBACK\nWndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)\n{\n    <FONT COLOR=\"blue\">if (uiMsg == WM_MENUSELECT) {\n        HANDLE_WM_MENUSELECT(hwnd, wParam, lParam, OnMenuSelect);\n    }<\/FONT>\n    if (g_pcm3) {\n&#8230;\n<\/PRE>\n<P>\nWait a second, there was a comment up there that said that\nthe OnMenuSelect function is buggy.  Where&#8217;s the bug?\n<\/P>\n<P>\nWell, technically there is no bug, but if you run this program as-is\n(and I suggest that you do),\nyou&#8217;ll find that what you get is rather erratic.\n<\/P>\n<P>\nThat&#8217;s because there are a lot of buggy context menu handlers\nout there.\n<\/P>\n<P>\nSome context menu handlers don&#8217;t support Unicode; others don&#8217;t\nsupport Ansi.  What&#8217;s really fun is that instead of returning\nE_NOTIMPL, they return S_OK but don&#8217;t actually do anything.\nOther context menus have buffer overflow problems and write\nto the buffer beyond the actual size you specified.\n<\/P>\n<P>\nWelcome to the world of application compatibility.\n<\/P>\n<P>\nLet&#8217;s write a helper function that tries to hide all of these\nweirdnesses.\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">HRESULT IContextMenu_GetCommandString(\n    IContextMenu *pcm, UINT_PTR idCmd, UINT uFlags,\n    UINT *pwReserved, LPWSTR pszName, UINT cchMax)\n{\n  \/\/ Callers are expected to be using Unicode.\n  if (!(uFlags &amp; GCS_UNICODE)) return E_INVALIDARG;<\/p>\n<p>  \/\/ Some context menu handlers have off-by-one bugs and will\n  \/\/ overflow the output buffer. Let&#8217;s artificially reduce the\n  \/\/ buffer size so a one-character overflow won&#8217;t corrupt memory.\n  if (cchMax &lt;= 1) return E_FAIL;\n  cchMax&#8211;;<\/p>\n<p>  \/\/ First try the Unicode message.  Preset the output buffer\n  \/\/ with a known value because some handlers return S_OK without\n  \/\/ doing anything.\n  pszName[0] = L&#8217;\\0&#8242;;<\/p>\n<p>  HRESULT hr = pcm-&gt;GetCommandString(idCmd, uFlags, pwReserved,\n                                     (LPSTR)pszName, cchMax);\n  if (SUCCEEDED(hr) &amp;&amp; pszName[0] == L&#8217;\\0&#8242;) {\n    \/\/ Rats, a buggy IContextMenu handler that returned success\n    \/\/ even though it failed.\n    hr = E_NOTIMPL;\n  }<\/p>\n<p>  if (FAILED(hr)) {\n    \/\/ try again with ANSI &#8211; pad the buffer with one extra character\n    \/\/ to compensate for context menu handlers that overflow by\n    \/\/ one character.\n    LPSTR pszAnsi = (LPSTR)LocalAlloc(LMEM_FIXED,\n                                      (cchMax + 1) * sizeof(CHAR));\n    if (pszAnsi) {\n      pszAnsi[0] = &#8216;\\0&#8217;;\n      hr = pcm-&gt;GetCommandString(idCmd, uFlags &amp; ~GCS_UNICODE,\n                                  pwReserved, pszAnsi, cchMax);\n      if (SUCCEEDED(hr) &amp;&amp; pszAnsi[0] == &#8216;\\0&#8217;) {\n        \/\/ Rats, a buggy IContextMenu handler that returned success\n        \/\/ even though it failed.\n        hr = E_NOTIMPL;\n      }\n      if (SUCCEEDED(hr)) {\n        if (MultiByteToWideChar(CP_ACP, 0, pszAnsi, -1,\n                                pszName, cchMax) == 0) {\n          hr = E_FAIL;\n        }\n      }\n      LocalFree(pszAnsi);<\/p>\n<p>    } else {\n      hr = E_OUTOFMEMORY;\n    }\n  }\n  return hr;\n}<\/FONT>\n<\/PRE>\n<P>\nThe shell has lots of strange functions like this.\n<\/P>\n<P>\n[<CODE>pszAnsi<\/CODE> comparison fixed, 29 September.]\n<\/P>\n<P>\nWith this helper function, we can fix our help text function.\n<\/P>\n<PRE>\nvoid OnMenuSelect(HWND hwnd, HMENU hmenu,\n                  int item, HMENU hmenuPopup, UINT flags)\n{\n  if (g_pcm &amp;&amp; item &gt;= SCRATCH_QCM_FIRST &amp;&amp;\n      item &lt;= SCRATCH_QCM_LAST) {\n    <FONT COLOR=\"blue\">WCHAR<\/FONT> szBuf[MAX_PATH];\n    if (FAILED(<FONT COLOR=\"blue\">IContextMenu_GetCommandString(g_pcm,<\/FONT>\n                                       item &#8211; SCRATCH_QCM_FIRST,\n                                       <FONT COLOR=\"blue\">GCS_HELPTEXTW<\/FONT>, NULL,\n                                       <FONT COLOR=\"blue\">szBuf<\/FONT>, MAX_PATH))) {\n      <FONT COLOR=\"blue\">lstrcpynW<\/FONT>(szBuf, <FONT COLOR=\"blue\">L<\/FONT>&#8220;No help available.&#8221;, MAX_PATH);\n    }\n    <FONT COLOR=\"blue\">SetWindowTextW<\/FONT>(hwnd, szBuf);\n  }\n}<\/FONT>\n<\/PRE>\n<P>\nThis new version displays help texts for all the context menu\nhandlers that support it, in spite of the attempts of many\nof those context menu handlers to get it wrong or even\ncreate a buffer overflow security vulnerability.\n<\/P>\n<P>\nOkay, that was quite a long digression from\n<A href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/09\/20\/231739.aspx\">\npart 1 of this series<\/A>.\nLet&#8217;s return to the subject of invoking the default verb\n<A href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/09\/30\/236133.aspx\">\nnext time<\/A>.\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the subtleties of context menus is showing help in the status bar. Now, the program we&#8217;ve been developing doesn&#8217;t have a status bar, so we&#8217;ll fake it by putting the help text in the title bar. The key method for this task is IContextMenu::GetCommandString, which allows communication with a context menu handler about [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-37723","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>One of the subtleties of context menus is showing help in the status bar. Now, the program we&#8217;ve been developing doesn&#8217;t have a status bar, so we&#8217;ll fake it by putting the help text in the title bar. The key method for this task is IContextMenu::GetCommandString, which allows communication with a context menu handler about [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/37723","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=37723"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/37723\/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=37723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=37723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=37723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}