Windows菜单的三种创建方法

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函数删除它。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值