The evolution of menu templates: 16-bit extended menus


Windows 95 introduced a new menu format, known as "extended menus". You declare these in a resource file with the MENUEX keyword. The 16-bit extended menu is really just a temporary stopping point on the way to the 32-bit extended menu, since the 16-bit form is supported only by the Windows 95 family of operating systems. It's sort of the missing link of menu templates.

Things start off the same as the 16-bit classic menu, with a structure I've been calling MENUHEADER16:

struct MENUHEADER16 {
 WORD wVersion;
 WORD cbHeaderSize;
 BYTE rgbExtra[cbHeaderSize-4];
};

The version number for extended menus is one instead of zero, and the cbHeaderSize now includes the size of the wVersion and cbHeaderSize fields in the header size count; therefore, the number of interstitial bytes four less than the value specified by the cbHeaderSize member.

Due to a bug in Windows 95 (and its descendants), the cbHeaderSize is ignored, and its value is assumed to be four. Fortunately, every version of the 16-bit resource compiler that supports 16-bit extended menu templates sets the cbHeaderSize to four. Consequently, nothing goes wrong in practice. And I suspect nobody has noticed this bug in the over fifteen years (not twenty-five as I had originally written) the code has been in existence.

Unlike the classic menu, there is a prefix structure that comes before the list of menu items.

struct MENUPREFIX16 {
 DWORD dwContextHelpID;
};

New to extended menus is the addition of context help IDs. These values can be set and retrieved programmatically with the GetMenuContextHelpId and SetMenuContextHelpId functions.

The template then continues with a packed array of structures I will call MENUITEMEX16:

struct MENUITEMEX16 {
 DWORD dwType;
 DWORD dwState;
 WORD  wID;
 BYTE  bFlags;
 CHAR  szText[]; // null terminated ANSI string
};

Whereas the members of the classic MENUITEM16 were designed to be passed to the function InsertMenu, the members of the extended MENUITEMEX16 were designed to be passed to the function InsertMenuItem. The dwType, dwState, and wID members correspond to the fType, fState, and wID members of the 16-bit MENUITEMINFO structure. Similarly, the szText goes into the dwItemData if the item requires a string. (If the item doesn't require a string, then the szText should be an empty string; i.e., should consist solely of the null terminator.)

Notice that a new feature of extended menus is that pop-up menus can have IDs as well as normal menu items.

The bFlags describes other information about the menu item, information that in the classic menu was hidden in spare bits in the wFlags. But here, the bFlags is where this information is kept. The following flags are currently defined:

0x01 This item is a pop-up submenu
0x80 This item is the last item in the menu

If indeed the bottom bit is set, then after the MENUITEMEX16 comes a description of the submenu, recursively. (Note that the submenu does not have a MENUHEADER16.)

As before, we'll illustrate this format with an example.

1 MENUEX 1000
BEGIN
  POPUP "&File", 200,,, 1001
  BEGIN
    MENUITEM "&Open\tCtrl+O", 100
    MENUITEM "", -1, MFT_SEPARATOR
    MENUITEM "&Exit\tAlt+X",  101
  END
  POPUP "&View", 201,,, 1002
  BEGIN
    MENUITEM "&Status Bar", 102,, MFS_CHECKED
  END
END

The resulting 16-bit extended menu template begins with the header:

0000  01 00          // wVersion = 1
0002  04 00          // cbHeaderSize = 4

Since this is the start of a menu, we get a context help ID:

0004  E8 03 00 00    // dwContextHelpID = 1000

After the context help ID come the menu items. Our first is a pop-up submenu, so the bFlags indicates that a submenu is coming:

0008  00 00 00 00    // dwType = MFT_STRING
000C  00 00 00 00    // dwState = 0
0010  C8 00          // wID = 200
0012  01             // bFlags = "pop-up submenu"
0013  26 46 69 6C 65 00 // "&File" + null terminator

Since we have a pop-up submenu, we recursively include a template for that submenu directly after the menu item template. Consequently, we begin with the context help ID:

0019  E9 03 00 00    // dwContextHelpID = 1001

And then the contents of the submenu:

001D  00 00 00 00    // dwType = MFT_STRING
0021  00 00 00 00    // dwState = 0
0025  64 00          // wID = 100
0027  00             // bFlags = 0
0028  26 4F 70 65 6E 09 43 74 72 6C 2B 4F 00
                     // "&Open\tCtrl+O" + null terminator

0035  00 08 00 00     // dwType = MFT_SEPARATOR
0039  00 00 00 00     // dwState = 0
003D  FF FF           // wID = -1
003F  00              // bFlags = 0
0040  00              // ""

0041  00 00 00 00     // dwType = MFT_STRING
0045  00 00 00 00     // dwState = 0
0049  65 00           // wID = 101
004B  80              // bFlags = "this is the last menu item"
004C  26 45 78 69 74 09 41 6C 74 2B 58 00
                     // "&Exit\tAlt+X" + null terminator

When we reach the end of the pop-up submenu, we pop up a level. Therefore, the next entries describe more top-level menu items.

0058  00 00 00 00     // dwType = MFT_STRING
005C  00 00 00 00     // dwState = 0
0060  C9 00           // wID = 201
0062  81              // bFlags = "pop-up submenu" |
                      //          "this is the last menu item"
0063  26 56 69 65 77 00 // "&View" + null terminator

Ah, no sooner do we pop up than we push back down with another submenu. And the "last menu item" flag is set, which means that once the submenu is finished, we are done with the extended menu template.

0069  EA 03 00 00    // dwContextHelpID = 1002

006D  00 00 00 00    // dwType = MFT_STRING
0071  08 00 00 00    // dwState = MFS_CHECKED
0075  66 00          // wID = 102
0077  80             // bFlags = "this is the last menu item"
0078  26 53 74 61 74 75 73 20 42 61 72 00
                     // "&Status Bar" + null terminator

After the context help ID, we have the sole menu item for this pop-up submenu, so the first item is also the last item.

Next time, we'll wrap up by looking at the final menu template format, the 32-bit extended menu. I bet you all can't wait.

Comments (12)
  1. Yuhong Bao says:

    In fact, this is the reason why 16-bit extended templates exist in the first place:

    http://blogs.msdn.com/oldnewthing/archive/2008/07/08/8705314.aspx#8709887

  2. Medinoc says:

    I beg your pardon, but there is an inconsistency:

    The field named “dwType” in the structure declaration is called “dwFlags” in the hex dump…

    [Oops, fixed. Thanks. -Raymond]
  3. Don't like to pick... says:

    …but do you really mean twenty-five years?

    [Oops. can’t do math. -Raymond]
  4. I would be tremendously indebted to you if I could convince you to change your "Exit" entry to:

    004C  45 26 78 69 74 09 41 6C 74 2B 58 00 // "E&xittAlt+X" + null terminator

  5. Or better still:

    004C  45 26 78 69 74 09 41 6C 74 2B 46 34 00 // "E&xittAlt+F4" + null terminator

  6. Joe says:

    I always wished the popup menus had an ID for the purpose of dynamically modifying a menu (e.g. adding an MRU list to the File menu).  Without the ID you have to do a string compare on the label which doesn’t hold up to translations and customizations.

    Too bad the VS resource editor doesn’t support IDs for popup menus.  The Visual Studio 2008 menu editor shows the ID property with a grayed out “ID cannot be edited” message.  If I manually set the id in the rc file it gets overwritten with 65535 the next time the file is saved by the visual menu editor.

    Hopefully the VS team will add support for this along with allowing toolbars with 256 color images.

  7. steveg says:

    Context Help IDs? They’re cool, but not exactly a new feature, more a convenience thing. In 3.x you could achieve (mostly) the same thing by tracking if the menus were open (WM_ENTERMENULOOP + WM_EXITMENULOOP, undocumented at the time, IIRC) in a global variable, and as each menu item was highlighted track the current menu ID in a different ID, and then use them when F1 was pressed.

    Well, that’s what I remember anyway, I don’t have the code lying around at work.

    Well-behaved apps of the day (well for a time, anyway) would also put help text in the status bar as each item was selected which was a dumb idea even when 640×480 was an astonishingly high resolution — except for one app, I forget which, put the menu help text in the title bar. Genius. Ugly, but genius. Tooltips saved the day.

  8. Koro says:

    steveg: I’ve seen this app. I think it was WordPerfect, or failing that, Lotus 1-2-3.

  9. Worf says:

    Maybe the ID for the popup menu allows one to pop up something if the popup itself is chosen rather than the submenu item?

    E.g., you can have Windows show Control Panels in a submenu to open individual control panels, or just click on Control Panels to see the normal window with all of ’em

  10. Neil says:

    The versions of the NetWare Administrator that I have do this, although they paint the text themselves which was a minor problem when only 16-bit versions were available, as they only assumed the presence of two window controls. (I haven’t tried the latest versions to see how well or otherwise they cope with Vista.)

  11. Medinoc says:

    So, SB_SIMPLE + SB_SETTEXT(SB_SIMPLEID) for menu help are no longer welcome ?

  12. steveg says:

    Apparently I made up the WM_ENTERMENULOOP thing above, I can’t find them in the source code in app I was thinking of (I know I used the WM_*LOOP items somewhere). Instead good old WM_MENUSELECT was where the status bar action happened.

    [code]

    case WM_MENUSELECT:

    if (lParam == 0)

    dwCurrentHelpId = ID_NONE;
    

    else if (HIWORD(wParam) & MF_POPUP)

    {

    }

    else

    dwCurrentHelpId = LOWORD(wParam);
    

    if (!(HIWORD(wParam) & MF_SEPARATOR) && !(HIWORD(wParam) & MF_POPUP))

    {

    LoadString(hInst, LOWORD(wParam), (LPSTR) szBuf, 80);
    
    g_theBumBar.SetText(0, (LPSTR) szBuf);
    
    }
    

    return(0L);

    [/code]

    (the SetText call is when we ported to 32 bit and sporadic C++. The custom status bar (BumBar) was replaced with the commdlg status bar)).

Comments are closed.