Win32汇编教程五
菜单和加速键的使用

本节的内容是上一节内容的扩展,所以示范的源程序是在上一节的基础上扩展的,在这儿下载本节的所有源程序

有关菜单和加速键

    菜单是Windows标准界面的最重要的组成部分,窗口的菜单条位于标题栏的下方,这个菜单通常被称为主菜单,列在主菜单下面的菜单项被称为下拉式菜单,或弹出式菜单、子菜单等,而在标题栏左边的图标上点击也会弹出一个菜单,叫做系统菜单。加速键实际上是菜单项的快捷键,应用程序常在菜单项的右边标出激活这个菜单项的快捷键,这就是加速键。菜单的结构是可嵌套的,也就是说,你可以在选择一个菜单项时弹出另一个菜单。菜单项的种类有正常的、被禁用的、灰化的、水平分隔线等。本节的示范程序演示了各种类型的菜单:你可以在主菜单中看到正常的和禁用的、灰化的菜单,可以用右键单击窗口的任一部分弹出一个“弹出式菜单”,也可以看到我在系统菜单中添加了几项新的内容。
    在编程的处理中,菜单是在资源文件中定义的(当然,你可以不用资源文件,而在程序中用AppendMenu一项一项的添加,但使用资源文件无疑是最简单的办法),然后在程序中用LoadMenu来获得菜单句柄再使用。在资源文件中定义菜单的语法如下:

菜单ID	menu	discardable
BEGIN
	popup	"主菜单项一"
	BEGIN
		menuitem	"弹出式菜单项一",	命令ID	[,OPTION]
		menuitem	"弹出式菜单项二",	命令ID	[,OPTION]
		menuitem	separator
		menuitem	"弹出式菜单项三",	命令ID	[,OPTION]
		...
	END
	popup	"主菜单项二"
	BEGIN
		menuitem	"弹出式菜单项一",	命令ID	[,OPTION]
		menuitem	"弹出式菜单项二",	命令ID	[,OPTION]
		menuitem	"弹出式菜单项三",	命令ID	[,OPTION]
		...
		popup		"嵌套的菜单项"
		BEGIN
		menuitem	"弹出式菜单项一",	命令ID	[,OPTION]
		menuitem	"弹出式菜单项二",	命令ID	[,OPTION]
		menuitem	"弹出式菜单项三",	命令ID	[,OPTION]
		...
		END
	END
	...
END

菜单ID就是我们在程序中用LoadMenu装入菜单用到的资源编号,menuitem separator 定义了分隔菜单项用的水平线,菜单项定义中的option是属性,如GRAYED是灰化的,INACTIVE是被禁用的等等。而加速键实际上就是定义了对应于各个菜单项的热键,定义方法如下:

加速键ID	accelerators
BEGIN
		VK_F1,	对应的菜单命令ID,	VIRTKEY
		VK_F2,	对应的菜单命令ID,	VIRTKEY
		...
		"A",	对应的菜单命令ID,	VIRTKEY,CONTROL
		"B",	对应的菜单命令ID,	VIRTKEY,CONTROL
END

其中,加速键ID是我们在程序中用LoadAccelerator装入加速键的资源编号,下面的每一项定义了一个键,VK_F1表示用F1,“A”表示键A,下面的VIRTKEY是必需的,再下面的CONTROL“或SHIFT、ALT”表示用CONTROL键组合,也就是说,如果你定义了:"C",IDM_COPY,VIRTKEY,CONTROL 而且在菜单定义中定义了 menuitem "拷贝",IDM_COPY,那么,你在程序中按下Ctrl-C实际上就是执行了菜单项“拷贝”。
    菜单和加速键的编程是很简单的,初始化的部分你需要做以下事情:

  1. 取得程序的实例句柄(hInstance)
  2. 用LoadMenu装入菜单,得到菜单句柄
  3. 用LoadAccelerator装入加速键,得到加速键句柄
  4. 注册窗口类
  5. 创建窗口时在参数中制定菜单句柄
  6. 显示窗口
  7. 然后进入消息循环,在消息循环中用TranslateAccelerator来进行加速键的检测(详见源程序)

当窗口显示后,当一个菜单项或一个加速键被按下时,Windows向窗口过程发送WM_COMMAND消息,而当一个系统菜单中的菜单项被按下时,Windows 向窗口过程发送WM_SYSCOMMAND,菜单项命令的ID就包括在wParam的低16位中,在一般的编程中,如果我们不对系统菜单消息进行处理,那么只需在WM_COMMAND消息的处理中建立一段 .if/.elseif/.elseif .../.endif的语句对各个菜单命令ID进行处理就行了。

使用菜单和加速键的源程序

		.386
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	Programmed by 罗云彬, bigluo@telekbird.com.cn
;	Website: http://asm.yeah.net
;	LuoYunBin's Win32 ASM page (罗云彬的编程乐园)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
		.model flat, stdcall
		option casemap :none   ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	Include 数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include		windows.inc
include		user32.inc
include		kernel32.inc
include		comctl32.inc
include		comdlg32.inc
include		shell32.inc
include		gdi32.inc

includelib	user32.lib
includelib	kernel32.lib
includelib	comctl32.lib
includelib	comdlg32.lib
includelib	shell32.lib
includelib	gdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	Equ 数据
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDI_MAIN	equ		1000		;icon
IDA_MAIN	equ		2000		;Accelerator

IDM_MAIN	equ		4000
IDM_OPEN	equ		4101
IDM_OPTION	equ		4102
IDM_EXIT	equ		4103
IDM_SETFONT	equ		4201
IDM_SETCOLOR	equ		4202
IDM_FIND	equ		4203
IDM_FINDPREV	equ		4204
IDM_FINDNEXT	equ		4205
IDM_TOOLBAR	equ		4206
IDM_TOOLBARTEXT	equ		4207
IDM_INPUTBAR	equ		4208
IDM_STATUSBAR	equ		4209
IDM_HELP	equ		4301
IDM_ABOUT	equ		4302

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		.data?

hIcon		dd		?
hInstance	dd		?
hWinMain	dd		?
hMenu		dd		?
hSubMenu	dd		?
szBuffer	db	256 dup	(?)
dwFlag		dd		?
;********************************************************************
;	标志位定义
F_TOOLBAR	equ	00000001b
F_TOOLBARTEXT	equ	00000010b
F_INPUTBAR	equ	00000100b
F_STATUSBAR	equ	00001000b

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		.data

szClassName	db	"Menu Example",0
szCaptionMain	db	'菜单应用示例',0
szMenuHelp	db	"帮助主题(&H)",0
szMenuAbout	db	"关于本程序(&A)...",0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

		.code

include		Debug.asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	程序开始
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
		call	_WinMain
		invoke	ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	主窗口程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain	proc
		local	@stWcMain:WNDCLASSEX
		local	@stMsg:MSG
		local	@hAccelerator

		invoke	InitCommonControls
		invoke	GetModuleHandle,NULL
		mov	hInstance,eax
		invoke	LoadIcon,hInstance,IDI_MAIN
		mov	hIcon,eax
		invoke	LoadMenu,hInstance,IDM_MAIN
		mov	hMenu,eax
;*************** 注册窗口类 *****************************************
		invoke	LoadCursor,0,IDC_ARROW
		mov	@stWcMain.hCursor,eax
		mov	@stWcMain.cbSize,sizeof WNDCLASSEX
		mov	@stWcMain.hIconSm,0
		mov	@stWcMain.style,CS_HREDRAW or CS_VREDRAW
		mov	@stWcMain.lpfnWndProc,offset WndMainProc
		mov	@stWcMain.cbClsExtra,0
		mov	@stWcMain.cbWndExtra,0
		mov	eax,hInstance
		mov	@stWcMain.hInstance,eax
		mov	@stWcMain.hIcon,0
		mov	@stWcMain.hbrBackground,COLOR_WINDOW + 1
		mov	@stWcMain.lpszClassName,offset szClassName
		mov	@stWcMain.lpszMenuName,0
		invoke	RegisterClassEx,addr @stWcMain
;*************** 建立输出窗口 ***************************************
		invoke	CreateWindowEx,WS_EX_CLIENTEDGE,\
			offset szClassName,offset szCaptionMain,\
			WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\
			100,100,550,300,\
			NULL,hMenu,hInstance,NULL

		invoke	ShowWindow,hWinMain,SW_SHOWNORMAL
		invoke	UpdateWindow,hWinMain
;*************** 消息循环 *******************************************
		invoke	LoadAccelerators,hInstance,IDA_MAIN
		mov	@hAccelerator,eax
		.while	TRUE
			invoke	GetMessage,addr	@stMsg,NULL,0,0
			.break	.if eax	== 0
			invoke	TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg
			.if	eax == 0
				invoke	TranslateMessage,addr @stMsg
				invoke	DispatchMessage,addr @stMsg
			.endif
		.endw
		ret

_WinMain	endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
WndMainProc	proc	uses ebx edi esi, \
		hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
		local	@stPos:POINT

		mov	eax,uMsg
		.if	eax ==	WM_CREATE
			mov	eax,hWnd
			mov	hWinMain,eax
			call	_Init
;********************************************************************
		.elseif	eax ==	WM_COMMAND
		   .if	lParam == 0
			mov	eax,wParam
			movzx	eax,ax
			.if	eax ==	IDM_EXIT
				call	_Quit
			.elseif	eax ==	IDM_TOOLBAR
				xor	dwFlag,F_TOOLBAR
				call	_MenuStatus
			.elseif	eax ==	IDM_TOOLBARTEXT
				xor	dwFlag,F_TOOLBARTEXT
				call	_MenuStatus
			.elseif	eax ==	IDM_INPUTBAR
				xor	dwFlag,F_INPUTBAR
				call	_MenuStatus
			.elseif	eax ==	IDM_STATUSBAR
				xor	dwFlag,F_STATUSBAR
				call	_MenuStatus
			.else
				_Debug	"菜单命令","命令ID",eax
			.endif
		   .endif
;********************************************************************
		.elseif	eax == WM_SYSCOMMAND
			mov	eax,wParam
			movzx	eax,ax
			.if	eax == IDM_HELP || eax == IDM_ABOUT
				_Debug	"菜单命令","命令ID",eax
			.else
				invoke	DefWindowProc,hWnd,uMsg,wParam,lParam
				ret
			.endif
;********************************************************************
;	按下右键时弹出一个POPUP菜单
;********************************************************************
		.elseif eax == WM_RBUTTONDOWN
			invoke	GetCursorPos,addr @stPos
			invoke	TrackPopupMenu,hSubMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL
;********************************************************************
		.elseif	eax ==	WM_CLOSE
			call	_Quit
;********************************************************************
		.else
			invoke	DefWindowProc,hWnd,uMsg,wParam,lParam
			ret
		.endif
;********************************************************************
;	注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0
;	但是由 DefWindowProc 处理后的返回值不能改变,否则窗口
;	将无法显示!
;********************************************************************
		xor	eax,eax
		ret

WndMainProc	endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	主窗口控制子程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Init		proc
		local	@hSysMenu

		invoke	SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon
;********************************************************************
;	POPUP菜单要用到子菜单才能实现
;********************************************************************
		invoke	GetSubMenu,hMenu,1
		mov	hSubMenu,eax
		call	_MenuStatus
;********************************************************************
;	在系统菜单中添加菜单项
;********************************************************************
		invoke	GetSystemMenu,hWinMain,FALSE
		mov	@hSysMenu,eax
		invoke	AppendMenu,@hSysMenu,MF_SEPARATOR,0,NULL
		invoke	AppendMenu,@hSysMenu,MF_STRING,IDM_HELP,offset szMenuHelp
		invoke	AppendMenu,@hSysMenu,MF_STRING,IDM_ABOUT,offset szMenuAbout

		ret

_Init		endp
;********************************************************************
;	根据标志位设置相应菜单项的状态
;********************************************************************
_MenuStatus	proc

		test	dwFlag,F_INPUTBAR
		.if	ZERO?
			invoke	CheckMenuItem,hMenu,IDM_INPUTBAR,MF_UNCHECKED
		.else
			invoke	CheckMenuItem,hMenu,IDM_INPUTBAR,MF_CHECKED
		.endif
		test	dwFlag,F_TOOLBAR
		.if	ZERO?
			invoke	CheckMenuItem,hMenu,IDM_TOOLBAR,MF_UNCHECKED
		.else
			invoke	CheckMenuItem,hMenu,IDM_TOOLBAR,MF_CHECKED
		.endif
		test	dwFlag,F_TOOLBARTEXT
		.if	ZERO?
			invoke	CheckMenuItem,hMenu,IDM_TOOLBARTEXT,MF_UNCHECKED
		.else
			invoke	CheckMenuItem,hMenu,IDM_TOOLBARTEXT,MF_CHECKED
		.endif
		test	dwFlag,F_STATUSBAR
		.if	ZERO?
			invoke	CheckMenuItem,hMenu,IDM_STATUSBAR,MF_UNCHECKED
		.else
			invoke	CheckMenuItem,hMenu,IDM_STATUSBAR,MF_CHECKED
		.endif
		ret

_MenuStatus	endp
;********************************************************************
_Quit		proc

		invoke	DestroyWindow,hWinMain
		invoke	PostQuitMessage,NULL
		ret

_Quit		endp
;********************************************************************
		end	start

程序的分析

    让我们来简单分析一下这个程序,首先这个程序和上一节的最简单的窗口程序的不同之处就是消息循环,如下:

		.while	TRUE
			invoke	GetMessage,addr	@stMsg,NULL,0,0
			.break	.if eax	== 0
			invoke	TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg
			.if	eax == 0
				invoke	TranslateMessage,addr @stMsg
				invoke	DispatchMessage,addr @stMsg
			.endif
		.endw 

在循环中的TranslateAccelerator用来确定存放在MSG结构中的消息是不是键盘消息,如果是,它查找句柄@hAccelerator对应的加速键表,如果找到了一个匹配项,那么它将用命令ID向窗口发送WM_COMMAND消息,同时返回非0值,这时候表示消息已经被处理,不用再调用下面的TranslateMessage 和 DispatchMessage 了,如果不是,那么它将返回0,消息循环继续。
    另外,要说明的是弹出式菜单,在程序中我们响应WM_RBUTTONDOWN消息对按下右键进行处理, 然后调用GetCursorPos取得当前鼠标坐标,然后使用TrackPopupMenu在鼠标位置上弹出一个菜单,但是在资源文件中,“弹出式菜单”是无法直接定义的,所以在初始化部分,我们使用GetSubMenu 取出弹出式子菜单的句柄供TrackPopupMenu使用。





(C) Copyright by LuoYunBin's Win32 ASM Page,http://asm.yeah.net