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. The program is winmenu.asm.

[Download source code.]

Back to Win32 ASM Page.

The program preliminaries

First the usual program startup -- up to just before the call to CreateWindowEx.
.386
.model  flat

; If using TLINK32
don't include vlib.inc
include vclib.inc    ; Microsoft VC++ .lib link names

include win32hst.inc    ; constants
structures
and entry names

.data
align   4
wcx     dd      size WNDCLASSEX           ; cbSize
dd      CS_VREDRAW or CS_HREDRAW  ; style
dd      WndProc                   ; lpfnWndProc
dd      0
0                       ; cbClsExtra
cbWndExtra
dd      0                         ; hInstance
dd      0                         ; hIcon
dd      0                         ; hCursor
dd      COLOR_WINDOW+1            ; hbrBackground
dd      0                         ; lpszMenuName
dd      wndclsname                ; lpszClassName
dd      0                         ; hIconSm

wndclsname db 'winmain'
0

.code

public _start
extrn   GetModuleHandle:near
extrn   LoadIcon:near
LoadCursor:near
extrn   RegisterClassEx:near

_start:
push    large 0         ; NULL string pointer means
call    GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
mov     [wcx.wcx_hInstance]
eax

push    large IDI_WINLOGO
push    large 0         ; hInstance
0 = stock icon
call    LoadIcon
mov     [wcx.wcx_hIcon]
eax

push    large IDC_ARROW
push    large 0         ; hInstance
0 = stock cursor
call    LoadCursor
mov     [wcx.wcx_hCursor]
eax

push    offset wcx
call    RegisterClassEx

.data
align   4
cwargs   dd   0                  ; dwExStyle
dd   wndclsname         ; lpszClass
dd   wnd_title          ; lpszName
dd   WS_VISIBLE or WS_OVERLAPPED or WS_SYSMENU or WS_THICKFRAME \
or WS_MINIMIZEBOX or WS_MAXIMIZEBOX  ; style
dd   100                ; x
dd   100                ; y
dd   400                ; cx (width)
dd   200                ; cy (height)
dd   0                  ; hwndParent
dd   0                  ; hMenu
dd   0                  ; hInstance
dd   0                  ; lpCreateParams

msgbuf MSG      <>

wnd_title db 'Window with menu'
0

.code

extrn   CreateWindowEx:near
extrn   GetMessage:near
DispatchMessage:near
extrn   ExitProcess:near

sub     esp
48            ; allocate argument list
mov     esi
offset cwargs ; set block move source
mov     edi
esp           ; set block move destination
mov     ecx
12            ; number of arguments
rep movsd
mov     eax
[wcx.wcx_hInstance]
mov     [esp+40]
eax      ; set hInstance argument in stack

Adding a menu to a window

Our program will define the menu template in memory. Here we create a menu "object" by calling LoadMenuIndirect which returns a menu handle. The menu is attached to the window we are creating by setting a 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.
.code

extrn   LoadMenuIndirect:near

push    offset appMenuTemplate
call    LoadMenuIndirect
mov     [esp+36]
eax    ; set hMenu argument in stack

The rest of the preliminaries

Here we finally call CreateWindowEx and add the message loop.
.code
call    CreateWindowEx

msg_loop:
push    large 0         ; uMsgFilterMax
push    large 0         ; uMsgFilterMin
push    large 0         ; hWnd (filter)
0 = all windows
push    offset msgbuf   ; lpMsg
call    GetMessage      ; returns FALSE if WM_QUIT
or      eax
eax
jz      end_loop

push    offset msgbuf
call    DispatchMessage

jmp     msg_loop

end_loop:
push    large 0 ; (error) return code
call    ExitProcess

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 exists 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
extrn   DefWindowProc:near
PostQuitMessage:near

WndProc:
mov     eax
[esp+4+4]   ; message ID
cmp     eax
WM_COMMAND  ; from menu
accelerator
or control
je      on_command
cmp     eax
WM_DESTROY  ; window will be destroyed
je      on_destroy
jmp     DefWindowProc   ; delegate other message processing

on_destroy:
push    large 0
call    PostQuitMessage

xor     eax
eax
ret     16

on_command:
mov     eax
[esp+4+8]   ; wParam
and     eax
large 0FFFFh        ; command ID
cmp     eax
IDM_EXIT
je      on_exit_command
cmp     eax
IDM_HELP
je      on_help_command
cmp     eax
IDM_ABOUT
je      on_about_command
xor     eax
eax         ; ignore other commands
ret     16

Menu actions

For terminating the program we send 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.

We use MessageBox to display messages when the non-exit menu items are selected.

.data
helpCaption equ wnd_title
helpText db 'Help not implemented.'
0

aboutCaption db 'About Win32 Assembly'
0
aboutText db 'Place your copyright here.'
0

.code
extrn   SendMessage:near
extrn   MessageBox:near

on_exit_command:
; trigger the main window close function
mov     eax
[esp+4+0]   ; get hWnd before stack changes
push    large 0         ; lParam
push    large 0         ; wParam
push    large WM_CLOSE  ; msgid
push    eax             ; hwnd
call    SendMessage

xor     eax
eax
ret     16

on_help_command:
mov     eax
[esp+4+0]   ; get hWnd before stack changes
push    large MB_OK
push    offset helpCaption
push    offset helpText
push    eax                     ; owner window
call    MessageBox

xor     eax
eax
ret     16

on_about_command:
mov     eax
[esp+4+0]   ; get hWnd before stack changes
push    large MB_OK
push    offset aboutCaption
push    offset aboutText
push    eax                     ; owner window
call    MessageBox

xor     eax
eax
ret     16

end     _start