Introduction to Menus
The Win32 API provides a number of functions to create menus dynamically
from scratch, but the preferred way is to create menu templates
and store them as resources in the EXE file. This is usually
accomplished with a menu editor and a resource compiler, but here we show
how to create a menu template without these special tools.
[Download source code.]
Adding a menu to a window
Our program, winmenu.asm, will define the menu template in memory.
Then it will create a menu "object" by calling LoadMenuIndirect, which
returns a menu handle. The menu can then be added at window creation time
by setting the menu handle in the appropriate CreateWindowEx argument.
You can also create a window without a menu and
later add it with SetMenu. This API function can also be used to change
menus. Menus can also be deleted with this API function by using a NULL
(zero) menu handle.
Menus can be further managed and manipulated via
their handles.
extrn LoadMenuIndirect:near
.code
; Place this code in InitApp
; ESI points to CreateWindowEx argument list
push offset appMenuTemplate
call LoadMenuIndirect
mov [esi].cwargMenu,eax ; add menu at creation time
DEFAULT_STYLE equ WS_VISIBLE + WS_OVERLAPPEDWINDOW
DEFAULT_EXSTYLE equ WS_EX_WINDOWEDGE + WS_EX_CLIENTEDGE
DEFAULT_X equ 100
DEFAULT_Y equ 100
DEFAULT_WIDTH equ 400
DEFAULT_HEIGHT equ 200
.data
align 4
cwargs CREATEARGS <DEFAULT_EXSTYLE,wndclsname,def_title,DEFAULT_STYLE, DEFAULT_X,DEFAULT_Y, DEFAULT_WIDTH,DEFAULT_HEIGHT, 0,0, 0, 0>
def_title db 'Window with menu',0
Menu template definition
The first two template entries are part of the template header. The two
entries are the menu template version number, and an offset to the list
of menu items. The offset allows the skipping of additional header information.
Version 0 is the original NT and older Win16 template. We will be using
the new version 1 template. It adds the F1 help ID to the header. Note
that the offset entry in the header is set to skip this extra information.
The header must be aligned on a DWORD boundary.
Each menu item that follows the header must also
be aligned on a DWORD boundary. There are five fields (entries) and a sixth
field which is supplied only for a popup menu item. They are, in order:
the item type, the item state, the item ID, menu structure info, the item
text, and the F1 help ID.
The item type field contains some valid combination
of MFT_ constants. It indicates the kinds of menu information to be shown.
Our example uses only the MFT_STRING and MFT_SEPARATOR types. The MFT_SEPARATOR
type produces the bar between the Help... and About... menu items.
The item state field contains some valid combination
of MFS_ constants. We use only the MFS_ENABLED type. It indicates a display
or selection state.
The item ID is the ID number sent via the WM_COMMAND
message to the window procedure that has the menu. Although the field is
32-bits, only 16-bits are sent via WM_COMMAND. The convention is to name
them as constants beginning with IDM_.
The next field is the menu structure info. It is
a byte field padded to a WORD boundary. Intel's little-endian number format
allows us to set it and the padding with a DW directive. The API does not
have standard names for the valid data of this field. We define MFR_ constants
for this purpose. There are only two: the MFR_POPUP flag signals that the
items following it are part of a popup submenu. The last item in the main
menu or submenu has the MFR_END flag in this field. The two flags can be
combined.
The item text is the text that will be displayed
by a MFT_STRING item. In spite of the fact that we use the "A" version
of LoadMenuIndirect, the strings in the template must be in wide Unicode
format. That's why DW is used instead of DB. As in C, strings are terminated
with a zero null (NUL) character. This field always exist because of the
need for padding to a DWORD boundary. An '&' in the item string causes
the character following it to show up underlined. The underlined character
is the keyboard menu selector. So if you press and release the Alt key,
the first item in the menu bar will be highlighted. Then press the 'F'
key, and the File submenu will pop up. If a submenu pops up, another keypress
can select an item on the submenu. To display '&' as a character, you
must double it as '&', '&'.
And finally the F1 help ID. This field must exist
for a MFR_POPUP entry; it must not exist for any other. It must be aligned
on a DWORD boundary.
;
; Menu item (command) IDs
;
IDM_EXIT equ 101
IDM_HELP equ 901
IDM_ABOUT equ 902
;
; Menu template
;
MFR_END equ 80h
MFR_POPUP equ 01h
.data
align 4 ; must align on DWORD boundary
appMenuTemplate dw 1 ; menu template version
dw 4 ; offset from end of this word to menu item list
dd 0 ; menu bar help ID
dd MFT_STRING,MFS_ENABLED,0
dw MFR_POPUP ; first column
dw '&','F','i','l','e',0,0 ; pad to align 4
dd 0 ; popup help ID
dd MFT_STRING,MFS_ENABLED,IDM_EXIT
dw MFR_END ; bottom of column
dw 'E','&','x','i','t',0,0 ; pad to align 4
dd MFT_STRING,MFS_ENABLED,0
dw MFR_POPUP or MFR_END ; second column, last one
dw '&','H','e','l','p',0,0 ; pad to align 4
dd 0 ; popup help ID
dd MFT_STRING,MFS_ENABLED,IDM_HELP
dw 0
dw '&','H','e','l','p','.','.','.',0
dd MFT_SEPARATOR,0,0
dw 0
dw 0 ; pad to align 4
dd MFT_STRING,MFS_ENABLED,IDM_ABOUT
dw MFR_END ; bottom of column
dw '&','A','b','o','u','t','.','.','.',0,0
Menu dispatch
When a menu item is selected and "activated", Windows sends a WM_COMMAND
message to the window containing the menu. The menu item ID functions as
the command ID passed to the window procedure in the lower sixteen bits
of wParam.
.code
WndProc:
mov eax,[esp+4+4] ; message ID
cmp eax,WM_COMMAND ; from menu, accelerator, or control
je execute_command
cmp eax,WM_DESTROY ; window will be destroyed
je start_destroy
jmp DefWindowProc ; delegate other message processing
;
; miscellaneous string data
;
.data
helpCaption equ def_title
helpText db 'Help not implemented.',0
aboutCaption db 'About Win32 Assembly',0
aboutText db 'Place your copyright here.',0
.code
execute_command:
mov eax,[esp+4+8] ; wParam
and eax,large 0FFFFh ; command ID in low bits
cmp eax,IDM_EXIT
je exit_command
cmp eax,IDM_HELP
je help_command
cmp eax,IDM_ABOUT
je about_command
xor eax,eax ; ignore other commands
ret 16
Menu actions
For terminating the program, we post a WM_CLOSE message to the "main" window.
This will cause a call to DestroyWindow, by default, and send a WM_DESTROY
message to our window procedure. The WM_DESTROY code is not shown here.
[See the minimal GUI program.]
We use MessageBox to display messages when the non-exit menu items are
selected.
extrn PostMessage:near
extrn MessageBox:near
extrn hwndMain:dword ; defined in winmain.asm
.code
exit_command:
; trigger the main window close function
push large 0 ; lParam
push large 0 ; wParam
push large WM_CLOSE ; msgid
push [hwndMain] ; hwnd
call PostMessage
xor eax,eax
ret 16
help_command:
mov eax,[esp+4+0] ; hWnd
push large MB_OK
push offset helpCaption
push offset helpText
push eax ; owner window
call MessageBox
xor eax,eax
ret 16
about_command:
mov eax,[esp+4+0] ; hWnd
push large MB_OK
push offset aboutCaption
push offset aboutText
push eax ; owner window
call MessageBox
xor eax,eax
ret 16