{"id":30933,"date":"2006-06-08T10:00:09","date_gmt":"2006-06-08T10:00:09","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2006\/06\/08\/the-forgotten-common-controls-the-menuhelp-function\/"},"modified":"2006-06-08T10:00:09","modified_gmt":"2006-06-08T10:00:09","slug":"the-forgotten-common-controls-the-menuhelp-function","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20060608-09\/?p=30933","title":{"rendered":"The forgotten common controls: The MenuHelp function"},"content":{"rendered":"<p>\nThe <code>MenuHelp<\/code> function is one of the more confusing\nones in the common controls library.\nFortunately, you will almost certainly never had need to use it,\nand once you learn the history of the <code>MenuHelp<\/code>\nfunction, you won&#8217;t <strong>want<\/strong> to use it anyway.\n<\/p>\n<p>\nOur story begins with 16-bit Windows.\nThe <code>WM_MENUSELECT<\/code> message is sent to notify\na window of changes in the selection state of a menu that\nhas been associated with the window,\neither by virtue of being the window&#8217;s menu bar or by\nhaving been passed as the owner window to a function like\n<code>TrackPopupMenu<\/code>.\nThe parameters to the <code>WM_MENUSELECT<\/code> message\nin 16-bit Windows were as follows:\n<\/p>\n<table>\n<tr>\n<td VALIGN=\"TOP\">wParam&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\">menu item ID&nbsp;<\/td>\n<td VALIGN=\"TOP\">if selection is a plain menu item<\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"TOP\"><\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\">pop-up menu handle&nbsp;<\/td>\n<td VALIGN=\"TOP\">if selection is a pop-up menu<\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"TOP\">lParam&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\" COLSPAN=\"2\">MAKELPARAM(flags, parent menu handle)&nbsp;<\/td>\n<\/tr>\n<\/table>\n<p>\nThe <code>MenuHelp<\/code> function\nparsed the parameters of the <code>WM_MENUSELECT<\/code> message\nin conjunction with a table describing the mapping between menu items\nand help strings,\ndisplaying the selected string in the status bar.\nThe information was provided in the confusing format of an array of\n<code>UINT<\/code>s that took the following format\n(expressed in pseudo-C):\n<\/p>\n<pre>\nstruct MENUHELPPOPUPUINTS {\n UINT uiPopupStringID;\n HMENU hmenuPopup;\n};\nstruct MENUHELPUINTS {\n UINT uiMenuItemIDStringOffset;\n UINT uiMenuIndexStringOffset;\n MENUHELPPOPUPUINTS rgwPopups[];\n};\n<\/pre>\n<p>\nThe <code>uiMenuItemIDStringOffset<\/code> specifies the value\nto add to the menu ID to convert it to a string ID that is\nto be displayed in the status bar.\nFor example, if you had\n<\/p>\n<pre>\n    MENUITEM \"&amp;New\\tCtrl+N\"    ,200\n<\/pre>\n<p>\nin your menu template, and you specified an offset of <code>1000<\/code>,\nthen the <code>MenuHelp<\/code> function will look for the help\nstring as string identifier\n<code>200 + 1000 = 1200<\/code>:\n<\/p>\n<pre>\nSTRINGTABLE BEGIN\n1200 \"Opens a new blank document.\"\nEND\n<\/pre>\n<p>\nThe <code>uiMenuIndexStringOffset<\/code> does the same thing for\npop-up menus that were direct children of the main menu,\nbut since pop-up menus in\n16-bit Windows didn&#8217;t have IDs, the zero-based menu index was used instead.\nFor example, if your menu had the top-level structure\n<\/p>\n<pre>\nBEGIN\n  POPUP \"&amp;File\"\n  BEGIN\n  ...\n  END\n  POPUP \"&amp;View\"\n  BEGIN\n  ...\n  END\nEND\n<\/pre>\n<p>\nand you specified a <code>uiMenuIndexStringOffset<\/code> of <code>800<\/code>,\nthen the string for the File menu was expected to be at\n<code>0 + 800 = 800<\/code> and the string for the\nView menu was expected at\n<code>1 + 800 = 801<\/code>.\n<\/p>\n<pre>\nSTRINGTABLE BEGIN\n800 \"Contains commands for working with the current document.\"\n801 \"Contains edit commands.\"\nEND\n<\/pre>\n<p>\nThe last case is a pop-up menu that is\na grandchild (or deeper descendant) of the main menu.\nAs we saw above,\nthe <code>WM_MENUSELECT<\/code> message encoded the\nhandle of the pop-up menu rather than its ID.\nThis handle was looked up in the variable-length array\nof <code>MENUHELPPOPUPUINTS<\/code> elements\n(terminated by a\n<code>{0,&nbsp;0}<\/code> entry).\nNotice that the second member of the\n<code>MENUHELPPOPUPUINTS<\/code> structure is an\n<code>HMENU<\/code> rather than a <code>UINT<\/code>.\nBut in 16-bit Windows,\n<code>sizeof(HMENU) == sizeof(UINT) == 2<\/code>,\nand 16-bit code (such as the\n<code>WM_MENUSELECT<\/code> message)\nrelied heavily on coincidences like this.\n<\/p>\n<p>\nIf a pop-up window had the handle, say,\n<code>(HMENU)0x1234<\/code>, the <code>MenuHelp<\/code>\nfunction would look for a\n<code>MENUHELPPOPUPUINTS<\/code> entry which had a\n<code>hMenuPopup<\/code> equal to\n<code>(HMENU)0x1234<\/code>,\nat which point it would use the corresponding\n<code>uiPopupStringID<\/code> as the help string.\n<\/p>\n<p>\nLet&#8217;s take a look at one of these in practice.\nHere&#8217;s a menu and a corresponding string table:\n<\/p>\n<pre>\n1 MENU\nBEGIN\n  POPUP \"&amp;File\"\n  BEGIN\n    MENUITEM \"&amp;New\\tCtrl+N\"    ,200\n    MENUITEM \"&amp;Open\\tCtrl+O\"   ,201\n    MENUITEM \"&amp;Save\\tCtrl+S\"   ,202\n    MENUITEM \"Save &amp;As\"        ,203\n    MENUITEM \"\"                ,-1\n    MENUITEM \"E&amp;xit\"           ,204\n  END\n  POPUP \"&amp;View\"\n  BEGIN\n    MENUITEM \"&amp;Status bar\"     ,240\n    MENUITEM \"&amp;Full screen\"    ,230\n    POPUP \"Te&amp;xt Size\"\n    BEGIN\n      MENUITEM \"&amp;Large\"        ,225\n      MENUITEM \"&amp;Normal\"       ,226\n      MENUITEM \"&amp;Small\"        ,227\n    END\n  END\nEND\nSTRINGTABLE BEGIN\n 800 \"Contains commands for loading and saving files.\"\n 801 \"Contains commands for manipulating the view.\"\n1200 \"Opens a new blank document.\"\n1201 \"Opens an existing document.\"\n1202 \"Saves the current document.\"\n1203 \"Saves the current document with a new name.\"\n1225 \"Selects large font size.\"\n1226 \"Selects normal font size.\"\n1227 \"Selects small font size.\"\n1230 \"Maximizes the window to full screen.\"\n1240 \"Shows or hides the status bar.\"\n2006 \"Specifies the relative size of text.\"\nEND\n<\/pre>\n<p>\nNotice that there was no requirement that the menu item identifiers\nbe consecutive.\nAll that the <code>MenuHelp<\/code> function cared about is that\nthe relationship between the menu item identifiers and the help strings\nwas in the form of a simple offset.\n<\/p>\n<p>\nThe table that connects the menu to the string table\ngoes like this:\n<\/p>\n<pre>\nUINT rguiHelp[] = {\n  1000,    \/\/ uiMenuItemIDStringOffset\n   800,    \/\/ uiMenuIndexStringOffset\n  2006, 0, \/\/ uiPopupStringID, placeholder\n     0, 0  \/\/ end of MENUHELPPOPUPUINTS\n};\n<\/pre>\n<p>\nSince there is a grandchild pop-up menu,\nwe created a placeholder entry that will be filled in\nwith the menu handle at run time:\n<\/p>\n<pre>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n  HMENU hmenuMain = GetMenu(hwnd);\n  HMENU hmenuView = GetSubMenu(hmenuMain, 1);\n  HMENU hmenuText = GetSubMenu(hmenuView, 2);\n  rguiHelp[3] = (UINT)hmenuText;\n  g_hwndStatus = CreateWindow(STATUSCLASSNAME, NULL,\n        WS_CHILD | CCS_BOTTOM | SBARS_SIZEGRIP | WS_VISIBLE,\n        0, 0, 0, 0, hwnd, (HMENU)100, g_hinst, 0);\n  return g_hwndStatus != NULL;\n}\n<\/pre>\n<p>\nWe locate the &#8220;Text Size&#8221; menu and put its menu handle\ninto the <code>rguiHelp<\/code> array so that the\n<code>MenuHelp<\/code> command can find it.\nThe window procedure would then include the line:\n<\/p>\n<pre>\n...\n    case WM_MENUSELECT:\n      MenuHelp(uiMsg, wParam, lParam, GetMenu(hwnd),\n               g_hinst, g_hwndStatus, rguiHelp);\n      break;\n...\n<\/pre>\n<p>\nThat last step finally connects all the pieces.\nWhen the <code>WM_MENUSELECT<\/code> message arrives,\nthe <code>MenuHelp<\/code> function looks at the item\nthat was selected, uses it to look up the appropriate\nstring resource, loads the resource from the provided\n<code>HINSTANCE<\/code> and displays it in the status bar.\n<\/p>\n<p>\nTo make the sample complete, we need to do a little extra\nbookkeeping:\n<\/p>\n<pre>\nHWND g_hwndStatus;\nvoid\nOnSize(HWND hwnd, UINT state, int cx, int cy)\n{\n  MoveWindow(g_hwndStatus, 0, 0, cx, cy, TRUE);\n}\n\/\/ change to InitApp\n    wc.lpszMenuName = MAKEINTRESOURCE(1);\n<\/pre>\n<p>\n(I&#8217;d invite you to code up this sample 16-bit program and run it,\nbut I doubt anybody would be able to take me up on the invitation\nsince very few people have access to a 16-bit compiler for\nWindows any more.)\n<\/p>\n<p>\nThis method works great for 16-bit code.\nBut look at what happened during the transition to 32-bit Windows:\nThe parameters to the <code>WM_MENUSELECT<\/code> message had\nto change since menu handles are 32-bit values.\nThere was no room in two 32-bit window message parameters\nto shove 48 bits of data (two window handles and 16 bits of flags).\nSomething had to give, and what gave was the pop-up menu handle.\nInstead of passing the handle, the index of the pop-up menu was\npassed in the message parameters.\nThis did not result in any loss of data since the menu handle could\nbe recovered by passing the parent menu handle and the pop-up menu index\nto the <code>GetSubMenu<\/code> function.\nThe repacking of the parameters thus goes like this:\n<\/p>\n<table>\n<tr>\n<td VALIGN=\"TOP\">LOWORD(wParam)&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\">menu item ID&nbsp;<\/td>\n<td VALIGN=\"TOP\">if selection is a plain menu item<\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"TOP\">&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\">pop-up menu index&nbsp;<\/td>\n<td VALIGN=\"TOP\">if selection is a pop-up menu<\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"TOP\">HIWORD(wParam)&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\">flags&nbsp;<\/td>\n<\/tr>\n<tr>\n<td VALIGN=\"TOP\">lParam&nbsp;<\/td>\n<td VALIGN=\"TOP\">=&nbsp;<\/td>\n<td VALIGN=\"TOP\" COLSPAN=\"2\">parent menu handle&nbsp;<\/td>\n<\/tr>\n<\/table>\n<p>\nThe array of <code>UINT<\/code>s therefore changed its meaning to\nmatch the new message packing:\n<\/p>\n<pre>\nstruct MENUHELPPOPUPUINTS {\n UINT uiPopupStringID;\n <font COLOR=\"blue\">UINT uiPopupIndex;<\/font>\n};\nstruct MENUHELPUINTS {\n UINT uiMenuItemIDStringOffset;\n UINT uiMenuIndexStringOffset;\n MENUHELPPOPUPUINTS rgwPopups[];\n};\n<\/pre>\n<p>\nThe advantage of changing the value from an <code>HMENU<\/code>\nto a <code>UINT<\/code> index is that the array does not need\nto be modified at run time.\nOkay, let&#8217;s actually try this.\nStart with\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nthe scratch program<\/a>,\nattach the resources I gave above,\nand use the following help array and code:\n<\/p>\n<pre>\nUINT rguiHelp[] = {\n  1000,    \/\/ uiMenuItemIDStringOffset\n   800,    \/\/ uiMenuIndexStringOffset\n  2006, 2, \/\/ uiPopupStringID, uiPopupMenuIndex\n  0,0      \/\/ end of MENUHELPPOPUPUINTS\n};\nHWND g_hwndStatus;\nvoid\nOnSize(HWND hwnd, UINT state, int cx, int cy)\n{\n  MoveWindow(g_hwndStatus, 0, 0, cx, cy, TRUE);\n}\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n  g_hwndStatus = CreateWindow(STATUSCLASSNAME, NULL,\n        WS_CHILD | CCS_BOTTOM | SBARS_SIZEGRIP | WS_VISIBLE,\n        0, 0, 0, 0, hwnd, (HMENU)100, g_hinst, 0);\n  return g_hwndStatus != NULL;\n}\n\/\/ add to WndProc\n    case WM_MENUSELECT:\n      MenuHelp(uiMsg, wParam, lParam, GetMenu(hwnd),\n               g_hinst, g_hwndStatus, rguiHelp);\n      break;\n\/\/ change to InitApp\n    wc.lpszMenuName = MAKEINTRESOURCE(1);\n<\/pre>\n<p>\nNotice that this is identical to the code needed for\nthe 16-bit <code>MenuHelp<\/code> function,\nexcept that we didn&#8217;t initialize the <code>UINT<\/code>\narray with the pop-up menu handle.\n<\/p>\n<p>\nRun this program and see how the help text in the status bar\nchanges based on which menu item you have seleced.\nThe <code>MenuHelp<\/code> function also knows about the\ncommands on the System menu and provides appropriate help text\nfor those as well.\n<\/p>\n<p>\nWow, this sounds like a neat function.\nWhy then did I say that you probably will decide that you\ndon&#8217;t want to use it?\nLet&#8217;s look at the limitations of the <code>MenuHelp<\/code>\nfunction.\n<\/p>\n<p>\nFirst, notice that all the help strings for the menu must\ncome from the same <code>HINSTANCE<\/code>.\nFurthermore, the offset from the menu item identifier to\nthe help string must remain constant across all menu items.\nThese two points mean that you cannot build a menu out of\npieces from multiple DLLs since you can pass only one\n<code>HINSTANCE<\/code> and offset.\n<\/p>\n<p>\nSecond, the fixed offset means that you cannot have menus\nwhose content expands dynamically, because you won&#8217;t have\nhelp strings for the dynamic content.\nWhat&#8217;s worse, if the dynamically-added menu item identifiers\nhappen to, when added to the fixed offset, coincide with some\nother string resource, that other string resource will be\nused as the help string!\nFor example, in our example above, if we dynamically added\na menu item whose identifier is 1000, then the\n<code>MenuHelp<\/code> function would look for the string whose\nresource identifier is <code>1000 + 1000 = 2000<\/code>.\nAnd if you happened to have some other string at position 2000\nfor some totally unrelated reason, that string will end up as the\nmenu help.\n<\/p>\n<p>\nBut hopefully you&#8217;ve spotted the fatal flaw in the\n<code>MenuHelp<\/code> function by now:\nThat pop-up menu index.\nI carefully designed this example to avoid the flaw.\nThe index of the &#8220;Text Size&#8221; pop-up menu is&nbsp;2, and it is\nthe only pop-up menu whose index is&nbsp;2.\n(The &#8220;File&#8221; menu is index&nbsp;0 and the &#8220;View&#8221; menu is index&nbsp;1.)\nIn real life, of course, you do not have the luxury of fiddling the\nmenus to ensure that no two pop-up menus have the same index.\nAnd when they end up with the same index,\nthe help strings get all confused since the <code>MenuHelp<\/code>\nfunction can&#8217;t tell which of the multiple &#8220;second pop-up menu&#8221;\nyou wanted to use string 2006 for.\n<\/p>\n<p>\nCould this be fixed?\nIf you tried to return to the old <code>HMENU<\/code>-based way\nof identifying pop-ups, you&#8217;d run into some new problems:\nFirst, the introduction of 64-bit Windows means that you cannot\njust cast an <code>HMENU<\/code> to a <code>UINT<\/code>\nbecause an <code>HMENU<\/code> is a 64-bit value and\n<code>UINT<\/code> is only 32 bits.\nYou could work around this by expanding the parameter to the\n<code>MenuHelp<\/code> function to be an array of\n<code>UINT_PTR<\/code> values instead of an array of\n<code>UINT<\/code>s,\nbut that&#8217;s not the only problem.\n<\/p>\n<p>\nThe <code>HMENU<\/code>-base mechanism supports only one window\nat a time since the global array needs to be edited for each\nclient.\nTo make it support multiple windows,\nyou would have to make a copy of the global array and edit\nthe private copy.\nTo avoid making a private copy, you would have to come up with\nsome other way of specifying the pop-up window.\n<\/p>\n<p>\nNow, you could spend even more time trying to come up with a\nsolution to the <code>HMENU<\/code> problem, but that still\nleaves the other problems we discussed earlier.\nTrying to salvage a <code>MenuHelp<\/code>-like solution to those\nproblems leads to even more\ncomplicated mechanisms for expressing the relationship\nbetween a menu item identifier and the corresponding help string.\nEventually, you come to the point where the general solution\nis too complicated for its own good and you&#8217;re better off just\ncoming up with an ad-hoc solution for your particular situation,\nlike we did when we\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2004\/09\/28\/235242.aspx\">\nadded menu help to our hosted shell context menus<\/a>.\n<\/p>\n<p>\n(The only people I see using the <code>MenuHelp<\/code> function\nignore dealing with pop-up menus and use only the first two\n<code>UINT<\/code>s, thereby avoiding the whole <code>HMENU<\/code> problem.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The MenuHelp function is one of the more confusing ones in the common controls library. Fortunately, you will almost certainly never had need to use it, and once you learn the history of the MenuHelp function, you won&#8217;t want to use it anyway. Our story begins with 16-bit Windows. The WM_MENUSELECT message is sent to [&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-30933","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The MenuHelp function is one of the more confusing ones in the common controls library. Fortunately, you will almost certainly never had need to use it, and once you learn the history of the MenuHelp function, you won&#8217;t want to use it anyway. Our story begins with 16-bit Windows. The WM_MENUSELECT message is sent to [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30933","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=30933"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/30933\/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=30933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=30933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=30933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}