Windows菜单的三种创建方法
菜单是窗体上的重要构件,是人机交互的重要工具。菜单由菜单条、子菜单、菜单项三个要素构成。
菜单条可以理解为"横式"菜单条,子菜单可以理解为"纵式"菜单条。因为"纵式"菜单条常挂入到其他菜单条中,这种情况称为子菜单;如果作为独立的快捷菜单使用,这种情况称为快捷菜单。
创建Windows菜单有三种方法,即资源编辑、内存菜单模板和动态创建。这三种方法可以独立使用,也可以结合使用。下面分别介绍。
1. 使用资源编辑器创建菜单
这是最常用也最方便的菜单创建方法,一般使用专用的资源编辑器来创建,几乎所有的编程器都配置了资源编辑器,实现了"所见即所得"的菜单创建方法。但也可以用Windows自带的普通文本编辑器来编写菜单,以下就用这种方法编写2个简单的菜单。
1.1 编写简单的菜单
用普通的纯文本编辑器(如Windows自带的普通文本编辑器)来写资源,需要将文本编辑器设置为"UTF-16 LE"编码格式,确保字符串不会出现乱码现象。
首先要新建一个资源文件。资源文件为普通文本文件,将文本文件后缀名改为".rc"即可,例rsrc.rc。
在rsrc.rc资源文件中编写以下内容:
#include "\masm32\include64\resource.h" //资源头文件
//菜单条ID
#define IDM_MAIN 100 //主菜单条ID
#define IDM_MENU1 101 //快捷菜单条ID
//菜单项ID
#define IDM_NEW 1001 //菜单项ID
#define IDM_EXIT 1002
#define IDM_ABOUT 1003
#define IDM_CUT 2001
#define IDM_COPY 2002
#define IDM_PASTE 2003
#define IDM_DEL 2004
//------------------------------
//定义一个主菜单
//------------------------------
IDM_MAIN MENUEX // 定义主菜单条
BEGIN
POPUP "文 件" //定义子菜单
BEGIN
MENUITEM "新 建", IDM_NEW
MENUITEM "退 出", IDM_EXIT
END
POPUP "帮 助"
BEGIN
MENUITEM "关 于", IDM_ABOUT
END
END
//------------------------------
//定义一个快捷菜单条
//------------------------------
IDM_MENU1 MENUEX // 定义快捷菜单条
{
POPUP "Edit"
{
MENUITEM "Cut", IDM_CUT
MENUITEM "Copy", IDM_COPY
MENUITEM "Paste", IDM_PASTE
MENUITEM MFT_SEPARATOR //分隔线
MENUITEM "Delete", IDM_DEL
}
}
这就是菜单资源的基本样式。在程序中可以调用LoadMenu函数装载该菜单,并将菜单与指定的窗口关联起来。
菜单块可以使用"BEGIN…END"或"{…}"来标识块的起止,效果是一样的。
一般情况下快捷菜单是通过GetSubMenu函数从主菜单的子菜单中获取,本例单独定义了一个IDM_MENU1菜单条,其中包含了一个"Edit"子菜单,以演示如果摘取快捷菜单的方法。
资源菜单装载示例:
;===========================================
;装入菜单
;入: hInst=菜单资源模块实例句柄
; =0: 默认为程序实例句柄
; hWin=窗口句柄。主菜单将关联到该窗口。
;返回: RAX=快捷菜单句柄
;注: 这个代码也演示了摘取快捷菜单的方法。
;===========================================
Load_Menu proc hInst:QWORD,hWin:QWORD
LOCAL ss_hMenu1:QWORD
LOCAL ss_hEditMenu:QWORD
;---检查实例句柄---
cmp hInst,0
jnz ss_1
invoke GetModuleHandle,0 ;程序实例句柄
mov hInst,rax
ss_1:
;---装载主菜单,并关联到窗口---
invoke LoadMenu,hInst,IDM_MAIN
; mov hMenu,rax
invoke SetMenu,hWin,rax
;------------------------------
;以下演示了摘取快捷菜单的方法。
;------------------------------
;---装载IDM_MENU1菜单---
invoke LoadMenu,hInst,IDM_MENU1
mov ss_hMenu1,rax
;---取序号为0的POPUP子菜单用作快捷菜单---
invoke GetSubMenu,rax,0
mov ss_hEditMenu,rax
;---将序号为0的POPUP子菜单移出IDM_MENU1菜单---
invoke RemoveMenu,ss_hMenu1,0,MF_BYPOSITION
;---删除IDM_MENU1菜单句柄---
invoke DestroyMenu,ss_hMenu1
mov rax,ss_hEditMenu
ret
Load_Menu endp
注意: 从IDM_MENU1菜单中摘取了唯一的一个POPUP子菜单后,IDM_MENU1菜单句柄被删除,不能再使用。必须先用RemoveMenu函数将子菜单移出,然后再删除IDM_MENU1菜单句柄。
1.2 资源菜单的语法
资源菜单的定义格式如下:
MenuID MENUEX
{
MENUITEM ItemText,Id,Type,State
...
POPUP ItemText,Id,Type,State,dwHelpId
{
MENUITEM ItemText,Id,Type,State
...
}
MENUITEM ItemText,Id,Type,State
...
}
上述语法中,Type、State、HelpID都可以省略,POPUP语句中的Id也可以省略。
其中MENUEX、MENUITEM、POPUP为关键字,分别定义菜单条、菜单项、子菜单。
各参数说明如下:
(1) MenuID: 菜单的标识,可以为小于65536整数,也可以为字符串。
(2) ItemText: 菜单项(或子菜单)标题字符串。
如果是分隔线,则为空串。分隔线的定义如下:
MENUITEM “”, , MFT_SEPARATOR
或简单写为:
MENUITEM MFT_SEPARATOR
(3) Id: 菜单项的命令标识,为小于65536整数。POPUP语句中的Id也可以省略。
(4) Type: 菜单项的类型志标位。这个志标与MENUITEMINFO结构中fType成员相同。(在下文中再介绍)
(5) State: 菜单项状态志标位。这个志标与MENUITEMINFO结构中fState成员相同。(在下文中再介绍)
(6) dwHelpId: 这是菜单上下文帮助标识ID。只有POPUP子菜单有该参数。如果省略则默认为零。
该参数的用法如下:
将鼠标放置在子菜单的某一菜单项上,然后按F1键,这时窗口消息处理过程会接收到一个WM_HELP消息,该消息的lParam参数指向一个HELPINFO结构。如果HELPINFO结构的iContextType成员为HELPINFO_MENUITEM,则说明是菜单帮助请求,否则为窗口或控件帮助清求。如果是子菜单发出的帮助请求,则dwContextId成员值为dwHelpId,iCtrlId成员值为鼠标所指的菜单项ID。这样就明确帮助请求是谁发送的,用户可以根据需要显示相关的帮助信息。
2. 使用内存菜单模板创建菜单
内存菜单模板不是定义在资源文件中,而是定义在源代码文件的数据段中。内存菜单模板有两种格式,即标准菜单模板和扩展菜单模板。因为上例的资源菜单定义中介绍的是MENUEX语句,其为扩展菜单定义语句,所以这里也介绍扩展菜单模板的使用方法,标准菜单模板使用方法与此类似。
扩展菜单模板使用两种结构,MENUEX_TEMPLATE_HEADER和MENUEX_TEMPLATE_ITEM,前者是菜单头结构,后者是菜单项结构。这两个结构都为描述性结构,用于说明菜单模板的格式,不存在于任何标准头文件中。
2.1 MENUEX_TEMPLATE_HEADER结构说明
MENUEX_TEMPLATE_HEADER STRUCT
wVersion WORD ?
wOffset WORD ?
dwHelpId DWORD ?
MENUEX_TEMPLATE_HEADER ENDS
(1) wVersion: 模板版本号。 对于扩展菜单模板,此成员必须为1。
(2) wOffset: 相对于此结构成员末尾的第一个MENUEX_TEMPLATE_ITEM 结构的偏移量。如果第一个菜单项紧跟dwHelpId成员定义,则此成员应为4。
(3) dwHelpId: 菜单栏的帮助标识符。即在资源菜单中提到过的dwHelpId参数,目前在Win10环境下的主菜单中该参数没有意义,可以设置为NULL。
2.2 MENUEX_TEMPLATE_ITEM结构说明
MENUEX_TEMPLATE_ITEM STRUCT
dwType DWORD ?
dwState DWORD ?
uId DWORD ?
wFlags WORD ?
szText WORD ?
dwHelpId DWORD ?
MENUEX_TEMPLATE_ITEM ENDS
(1) dwType: 菜单项的类型志标位。这个志标与MENUITEMINFO结构中fType成员相同。(在下文中再介绍)
(2) dwState: 菜单项状态志标位。这个志标与MENUITEMINFO结构中fState成员相同。(在下文中再介绍)
(3) uId=菜单项标识符。
(4) wFlags=项目定义志标位。可以是零或多个值的组合。对于32位和64位应用程序,此成员是一个WORD变量;对于16位应用程序,它是一个BYTE变量。
该成员可以是以下值之一或两者组合。
MFR_END=80h: 表示是菜单栏、下拉菜单、子菜单或快捷菜单中的最后一个菜单项。相当于资源定义中的"}"符号。
MFR_POPUP=01h: 表示定义一块下拉菜单或子菜单的项。相当于资源定义中的POPUP关键字。
(5) szText=菜单项文本。此成员是一个以NULL结尾的Unicode字符串。不管是用LoadMenuIndirectA或用LoadMenuIndirectW装载菜单模板,此成员必须为Unicde字符串。
(6) dwHelpId=下拉菜单或子菜单的帮助标识符。该成员位于4字节对齐的边界。
注意: 只有当wFlags有MFR_POPUP位时,才有该成员,否则无该成员。
每一个MENUEX_TEMPLATE_HEADER结构必须位于4字节对齐的边界,即DWORD上边界对齐。
2.3 扩展菜单模板示例
以下是一个扩展菜单模板,该模板定义了一个主菜单,主菜单中有3个项目,分别为"File"、“Edit"和"About”,其中前2项为子项目。
模板中的WSTR关键字是Masm64的Unicode字符串定义宏。该宏只能使用ASCII字符,所以没有使用中文字。
;---ID定义---
IDM_FILE EQU 100 ;主菜单条ID
IDM_NEW EQU 1001
IDM_EXIT EQU 1002
IDM_ABOUT EQU 1003
IDM_EDIT EQU 101 ;子菜单条ID
IDM_CUT EQU 2001
IDM_COPY EQU 2002
IDM_PASTE EQU 2003
IDM_DEL EQU 2004
IDM_UNDO EQU 2005
IDM_SELECTALL EQU 2006
MFR_END EQU 80h ;一个菜单条中的最后一个菜单项。
MFR_POPUP EQU 01h ;子菜单标识位。
.data
;-----------------
; 扩展菜单模板
;-----------------
align 4 ;4字节对齐
;---MENUEX_TEMPLATE_HEADER结构---
Menu_TempEx \
dw 1 ;扩展菜单模板版本号。此成员必须为 1。
dw 4 ;首个 MENUEX_TEMPLATE_ITEM 结构的偏移
dd 0 ;菜单上下文帮助标识(保留)。
;---以下紧跟菜单项(每项一个MENUEX_TEMPLATE_ITEM结构)---
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_FILE ;菜单项ID
dw MFR_POPUP ;wFlags。表示为子菜单
WSTR ,"File" ;菜单标题串(Unicode)
align 4 ;dwHelpId成员必须是4字节对齐
dd 0 ;HELPID (只有MFR_POPUP类有该项)
;--------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_NEW ;菜单项ID
dw 0h ;wFlags
WSTR ,"New"
;--------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_EXIT ;菜单项ID
dw MFR_END ;该子菜单条的最后一项
WSTR ,"Exit"
;--------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_EDIT ;菜单项ID
dw MFR_POPUP
WSTR ,"Edit"
dd 0 ;HELPID (只有MFR_POPUP类有该项)
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_UNDO ;菜单项ID
dw 0h ;wFlags;
WSTR ,"Undo"
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_COPY ;菜单项ID
dw 0h ;wFlags;
WSTR ,"Copy"
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_CUT ;菜单项ID
dw 0h ;wFlags;
WSTR ,"Cut"
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_PASTE ;菜单项ID
dw 0h ;wFlags;
WSTR ,"Paste"
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_DEL ;菜单项ID
dw 0h ;wFlags;
WSTR ,"Delete"
;-----------------
align 4
dd MFT_SEPARATOR ;分隔线
dd 0 ;菜单项状态
dd 0
dw 0
dw 0
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_SELECTALL
dw MFR_END
WSTR ,"Select All"
;-----------------
align 4
dd MFT_STRING ;菜单项类型
dd MFS_ENABLED ;菜单项状态
dd IDM_ABOUT
dw MFR_END
WSTR ,"About"
使用以下函数装载菜单模板,并关联到指定的窗口。
.code
;===========================================
;装入内存菜单模板,并关联到窗口
;入: hWin=窗口句柄。主菜单将关联到该窗口。
;===========================================
Load_MenuTemplate proc hWin:QWORD
invoke LoadMenuIndirect,ADDR Menu_TempEx
invoke SetMenu,hWin,rax
ret
Load_MenuTemplate endp
3. 菜单的动态创建
动态创建菜单是非常灵活的一种方法,可以在程序的运行过程中随时创建或删除菜单。并不是所有的菜单都需要统一用一种方法创建,而是可以多种方法结合使用。因为不管用什么方法,菜单被创建或装载后,后续的操作方法都相同。
菜单的很多操作都要用到一个MENUITEMINFO结构,所以了解MENUITEMINFO结构是非常必要的。
3.1 MENUITEMINFO结构说明
MENUITEMINFO STRUCT
cbSize UINT ?
fMask UINT ?
fType UINT ?
fState UINT ?
wID UINT ?
DWORD ? ;用于对齐
hSubMenu HMENU ?
hbmpChecked HBITMAP ?
hbmpUnchecked HBITMAP ?
dwItemData ULONG_PTR ?
dwTypeData LPTSTR ?
cch UINT ?
DWORD ? ;用于对齐
hbmpItem HBITMAP ?
MENUITEMINFO ENDS
(1) cbsize=结构尺寸。使用该结构时,首先设置该成员值,例:
LOCAL mii:MENUITEMINFO
mov mii.cbSize,sizeof MENUITEMINFO
(2) fMask=指定要返回或设置的成员。该成员可以是以下值中的一个或多个:
MIIM_BITMAP : 接收或设置hbmpItem成员。
MIIM_CHECKMARKS: 接收或设置hbmpChecked和hbmpUnchecked成员。
MIIM_DATA : 接收或设置dwItemData成员。
MIIM_FTYPE : 接收或设置fType成员。
MIIM_ID : 接收或设置wID成员。
MIIM_STATE : 接收或设置fState成员。
MIIM_STRING : 接收或设置dwTypeData成员。
MIIM_SUBMENU : 接收或设置hSubMenu成员。
MIIM_TYPE : 接收或设置fType和dwTypeData成员
MIIM_TYPE被MIIM_BITMAP, MIIM_FTYPE和MIIM_STRING替换。
(3) fType=菜单项类型。只有当fMask成员设置了MIIM_TYPE时,才能使用fType成员。
该成员可以是以下值中的一个或多个。
MFT_MENUBARBREAK: 将菜单项放在新行(对于菜单栏)或新列(对于下拉菜单、子菜单或快捷菜单)上。对于下拉菜单、子菜单或快捷菜单,用竖线将新列与旧列隔开。
MFT_MENUBREAK: 在一个新行(菜单条)或新列(下拉式菜单、子菜单和快捷菜单)上放置菜单项。对于下拉式菜单、子菜单和快捷菜单,无垂直分隔线。
MFT_OWNERDRAW: 自画的菜单。此时在显示该类菜单前,窗口过程将接收一个WM_MEASUREITEM 消息, 而每次更新菜单时将接收一个WM_DRAWITEM消息。如该值被设定,则dwTypeData 成员包含一个应用程序定义值。
MFT_RADIOCHECK: 菜单含有单选按钮标记。如果hbmpChecked成员为NULL,一个单选按钮标记显示在菜单上。
MFT_RIGHTJUSTIFY: 右对齐菜单项和任何后来的项。只对在菜单条上的菜单项有效。
MFT_SEPARATOR: 指定一分隔符(横线)。dwTypeData和cch成员忽略。如果用于主菜单中,则没有分隔线,只是插入一个间距。
MFT_STRING: 用一字串显示菜单。dwTypeData成员指向该字串。发果在fMask中指定了MIIM_STRING和MIIM_TYPE,则不能使MFT_STRING。
(4) fState=菜单项状态。fMask成员设置了MIIM_STATE时才能使用fState。
该成员可以是这些值中的一个或多个。
MFS_CHECKED : 显示菜单项被选中的标记。
MFS_DEFAULT : 指定默认菜单项。
MFS_DISABLED : 灰色无效。
MFS_ENABLED : 有效。
MFS_GRAYED : 灰色无效。
MFS_HILITE : 高亮。
MFS_UNCHECKED: 不选中。
MFS_UNHILIT : 不高亮。
(5) wID=一个应用程序定义的值,用于标识菜单项。设置fMask为MIIM_ID,才能使用wID。
(6) hSubMenu=与菜单项关联的下拉菜单或子菜单的句柄。设置fMask为MIIM_SUBMENU才能使用hSubMenu。
(7) hbmpChecked=菜单项处于选中状态时显示在菜单项左侧的位图的句柄。所谓"选中",即菜单项状态标志有MFS_CHECKED位。
如果该成员为NULL,则显示默认位图。如果指定了MFT_RADIOCHECK类型值,则默认位图为项目符号(子弹)。否则,它是一个选中标记(打勾的符号)。
设置fMask为MIIM_CHECKMARKS来使用hbmpChecked。
(8) hbmpUnchecked=菜单项处于未选中状态时显示在菜单项左侧的位图的句柄。所谓"未选中",即菜单项状态标志没有MFS_CHECKED位。
如果该成员为NULL,则不显示位图。
将fMask设置为MIIM_CHECKMARKS来使用hbmpUnchecked。
(9) dwItemData=与菜单项关联的应用程序定义的值。设置fMask为MIIM_DATA,使用dwItemData。
(10) dwTypeData=菜单项标题串。该成员的含义取决于fType的值,仅当在fMask成员中设置了MIIM_TYPE标志时才使用。
(11) cch=接收菜单项标题串时,为菜单项标题串的长度(以字符为单位)。设置菜单项标题串时,cch会被忽略。
当在fMask成员中设置MIIM_STRING标志时使用cch成员。
(12) hbmpItem=要显示的位图的句柄。当在fMask成员中设置MIIM_BITMAP标志时使用。
该位图跟选中状态无关,即菜单项状态标志不管是否有MFS_CHECKED位,均显示该位图。
MENUITEMINFO结构的成员和志标很多,在后续的具体应用中再进一步说明。
在资源菜单和内存菜单模板中都有一个上下文帮助标识参数dwHelpId,但MENUITEMINFO结构中并没有这个参数,因为它并不常用,如果需要的话可以调用SetMenuContextHelpId函数去设置,下面的示例中使用了这个函数。
3.2 编写三个函数
为了方便动态创建菜单,需要编写3个函数,这3个函数为Menu_AddItem、Menu_AddSeparator、Menu_AddSub,分别用于添加菜单项、菜单分隔线、子菜单。
;=========================================================
;添加一个菜单项目.
;入: hmenu=菜单句柄
; iItem=菜单项插入位置。由isPos指标它的含义。
; =-1: 添加到尾部。此时isPos无意义。
; isPos=TRUE: iItem为菜单位置序号
; =FALSE: iItem为菜单ID号
; pTitle=菜单项标题串地址
; tType=标题串类型:
; =0 : 为Unicode
; <>0: 为UTF-8
; uId=菜单项ID。
;=========================================================
Menu_AddItem proc hmenu:QWORD,iItem:DWORD,isPos:DWORD,\
pTitle:QWORD,tType:DWORD,uId:DWORD
LOCAL ss_mii:MENUITEMINFO
LOCAL ss_buf[MAX_PATH]:WORD
mov ss_mii.cbSize, sizeof MENUITEMINFO
mov ss_mii.fMask, MIIM_ID or MIIM_STRING
mov eax,uId
mov ss_mii.wID,eax
mov ss_mii.dwTypeData,r9 ;pTitle
cmp tType,0
jz ss_1
invoke MultiByteToWideChar,CP_UTF8,0,pTitle,-1,ADDR ss_buf,MAX_PATH
lea rax,ss_buf
mov ss_mii.dwTypeData,rax
ss_1:
cmp iItem,-1
jnz ss_2
mov isPos,TRUE
ss_2:
invoke InsertMenuItemW,hmenu,iItem,isPos,ADDR ss_mii
ret
Menu_AddItem endp
;================================================
;添加一个菜单项分隔条.
;入: hmenu=菜单句柄
; iItem=菜单项插入位置。由isPos指标它的含义。
; =-1: 添加到尾部。此时isPos无意义。
; isPos=TRUE: iItem为菜单位置序号
; =FALSE: iItem为菜单ID号
; uId=分隔条标识ID。分隔条也可以有ID。
; 一般将主菜单分隔条ID设为0,子菜单设为-1。
; 在绘制菜单时有时很有用。
;================================================
Menu_AddSeparator proc hmenu:QWORD,iItem:DWORD,\
isPos:DWORD,uId:DWORD
LOCAL ss_mii:MENUITEMINFO
mov ss_mii.cbSize,sizeof MENUITEMINFO
mov ss_mii.fMask, MIIM_ID or MIIM_TYPE
mov ss_mii.fType,MFT_SEPARATOR
mov eax,uId
mov ss_mii.wID,eax
cmp iItem,-1
jnz ss_1
mov isPos,TRUE
ss_1:
invoke InsertMenuItem,hmenu,iItem,isPos,addr ss_mii
ret
Menu_AddSeparator endp
;=========================================================
;在主菜单中添加一个子菜单.
;入: hmenu=主菜单句柄
; iItem=菜单项插入位置。由isPos指标它的含义。
; =-1: 添加到尾部。此时isPos无意义。
; isPos=TRUE: iItem为菜单位置序号
; =FALSE: iItem为菜单ID号
; hSub=子菜单句柄。须由CreatePopupMenu创建
; 或从其他主菜单中摘取的子菜单句柄。
; pTitle=菜单项标题串地址
; tType=标题串类型:
; =0 : 为Unicode
; <>0: 为UTF-8
; uId=子菜单标识ID。子菜单也可以有ID。
; 在绘制菜单时有时很有用。
;=========================================================
Menu_AddSub proc hmenu:QWORD,iItem:DWORD,isPos:DWORD,\
hSub:QWORD,pTitle:QWORD,tType:DWORD,uId:DWORD
LOCAL ss_mii:MENUITEMINFO
LOCAL ss_buf[MAX_PATH]:WORD
mov ss_mii.cbSize, sizeof MENUITEMINFO
mov ss_mii.fMask,MIIM_ID or MIIM_STRING or MIIM_SUBMENU
mov eax,uId
mov ss_mii.wID,eax
mov ss_mii.hSubMenu,r9 ;hSub
mov rax,pTitle
cmp tType,0
jz ss_1
invoke MultiByteToWideChar,CP_UTF8,0,pTitle,-1,ADDR ss_buf,MAX_PATH
lea rax,ss_buf
ss_1:
mov ss_mii.dwTypeData,rax
cmp iItem,-1
jnz ss_2
mov isPos,TRUE
ss_2:
invoke InsertMenuItemW,hmenu,iItem,isPos,ADDR ss_mii
ret
Menu_AddSub endp
3.3 动态创建菜单示例
动态创建菜单用到2个API函数,其中CreateMenu函数用于创建主菜单,CreatePopupMenu函数用于创建子菜单或快捷菜单。
注: 因为本人习惯使用Windows自带的普通文本编辑器来写源码,并使用"UTF-8"编码格式,所以以下示例中的字符串均为UTF-8字符串。
;=========================================
;创建菜单
;入: hWin=窗口句柄。主菜单将关联到该窗口。
;返回: RAX=主菜单句柄
;=========================================
New_Menu proc hWin:QWORD
LOCAL ss_hMenu:QWORD
LOCAL ss_hFileMenu:QWORD
LOCAL ss_hEditMenu:QWORD
;---先创建1个主菜单和2个子菜单---
call CreateMenu
mov ss_hMenu,rax ;主菜单句柄
call CreatePopupMenu
mov ss_hFileMenu,rax ;文件子菜单句柄
call CreatePopupMenu
mov ss_hEditMenu,rax ;编辑子菜单句柄
;---对子菜单设置帮助标识(相当于资源菜单中的dwHelpId。这不是必须的)---
invoke SetMenuContextHelpId,ss_hFileMenu,101 ;dwHelpId=101
invoke SetMenuContextHelpId,ss_hEditMenu,102 ;dwHelpId=102
;---添加文件子菜单的菜单项---
invoke Menu_AddItem,ss_hFileMenu,-1,0, "新 建",1,IDM_NEW
invoke Menu_AddItem,ss_hFileMenu,-1,0, "退 出",1,IDM_EXIT
;---添加编辑子菜单的菜单项---
invoke Menu_AddItem,ss_hEditMenu,-1,0, "剪 切",1,IDM_CUT
invoke Menu_AddItem,ss_hEditMenu,-1,0, "复 制",1,IDM_COPY
invoke Menu_AddItem,ss_hEditMenu,-1,0, "粘 贴",1,IDM_PASTE
invoke Menu_AddSeparator,ss_hEditMenu,-1,0,-1 ;分隔线
invoke Menu_AddItem,ss_hEditMenu,-1,0, "删 除",1,IDM_DEL
;---将2个子菜单添加到主菜单中---
invoke Menu_AddSub,ss_hMenu,-1,0,ss_hFileMenu,"File",1,101
invoke Menu_AddSub,ss_hMenu,-1,0,ss_hEditMenu,"Edit",1,102
;---在主菜单的右端添加一个"关 于"菜单项---
invoke Menu_AddItem,ss_hMenu,-1,0, "关 于",1,IDM_ABOUT
;---将主菜单关联到窗口---
invoke SetMenu,hWin,ss_hMenu
invoke DrawMenuBar,hWin ;重绘菜单条
mov rax,ss_hMenu
ret
New_Menu endp
3.4 动态删除菜单
菜单可以动态创建,也可以动态删除。删除菜单很简单,使用RemoveMenu函数即可。
RemoveMenu函数的类型如下:
BOOL RemoveMenu(
[in] HMENU hMenu,
[in] UINT uPosition,
[in] UINT uFlags
);
(1)hMenu=要更改的菜单的句柄。
(2)uPosition=要删除的菜单项,由uFlags参数确定。
(3) uFlags=指示如何解释uPosition参数。该参数必须是以下值之一。
MF_BYCOMMAND=00h: 指示uPosition提供菜单项的标识符。
MF_BYPOSITION=400h: 指示uPosition给出菜单项的从零开始的相对位置。
RemoveMenu函数删除菜单项或从指定菜单分离子菜单。即如果uPosition所指的是子菜单,则该子菜单移出主菜单,但子菜单句柄并没有删除,可以再次添加到主菜单,或作为快捷菜单使用,如果不再需要了,则可调用DestroyMenu函数删除它。
注意:
无论菜单是否在显示窗口中,只要菜单发生变化,应用程序就必须调用DrawMenuBar函数更新菜单条。
程序退出时,如果菜单或子菜单被挂入到窗口中,则系统会自动释放这些菜单句柄;如果菜单没有挂入到窗口,或快捷菜单一是菜单中的子菜单,则需用户DestroyMenu函数删除它。