Windows菜单位图使用与菜单绘制
Windows菜单位图使用与菜单绘制的基本内容在Windows文档使用菜单中有详细说明,但涉及菜单的绘制方面的介绍只是简单的一些内容。本文比较全面地介绍菜单位图的使用技巧与菜单绘制的方法。
菜单绘制是窗口要素绘制中最具有挑战性的活,所涉用的内容比较多,所以本文提供全部源码。
在介绍菜单绘制前,必须先编写一些基本的实用函数,使得条理更清楚。
1. 菜单状态位图的操作技巧
状态位图就是菜单的单(复)选位图,默认的单选位图是一个圆点,复选位图是一个勾子,用户可以使用SetMenuItemInfo函数设置自己的位图。
但使用单(复)选状态位图有两个不方便的地方,第一是复选菜单项并没有在MENUITEMINFO结构的fType成员中有对应的志标位,无法识别某一菜单项是否为复选菜单项;第二是单选菜单项没有组别的信息,使用CheckMenuRadioItem函数去更新一组单选菜单项状态时需要提供该组的最小菜单ID与最大菜单ID。因为这两个问题的存在,我们没有办法编写一个共用的函数来自动处理单(复)选状态,虽然可以使用MENUITEMINFO结构的dwItemData成员设置附加志标来解决这个问题,但dwItemData成员在某些地方是很有用的,如果要编写一个共用的处理函数不建议去占用它。
为了编写一个菜单的单(复)选状态自动处理函数,本人通过在菜单ID中设置志标位来解决上述问题。将所有复选菜单ID的最高位置为"1",例将8001h、8002h等值作为复选菜单ID。单选菜单需要3个标识值,一个菜单ID为一个16位值,共有4个十六进制值,最低2个值用作单选组组内序号,并约定组内首个菜单ID的内部序号为01;第3个值用作单选组组内项目数;第4个值用作单选组组号。
例:
;复选菜单ID
IDM_WRAP EQU 8301h
;第1组单选菜单ID
IDM_SIMPLE EQU 1301h
IDM_TRAD EQU 1302h
IDM_ENGLISH EQU 1303h
;第2组单选菜单ID
IDM_LEFT EQU 2401h
IDM_TOP EQU 2402h
IDM_DEFAULT EQU 2403h
IDM_SNAP EQU 2404h
下面编写一个通用函数来处理菜单。
根据上述的约定,定义几个常量:
;--------------------------------------------------
;菜单ID志标: 单选组组内首个菜单的组内序号必须为01h。
;--------------------------------------------------
MENU_CHECKITEM EQU 8000h ;复选菜单项志标位
MENU_RADIO_GIDMK EQU 0f000h ;单选组组号掩码(0-f)
MENU_RADIO_GNMK EQU 0f00h ;单选组组内项目数掩码(0-f)
MENU_RADIO_IIDMK EQU 0ffh ;单选组组内序号掩码(1-ff)
;--------------------------------------------------
;===========================================
;改变菜单项的单(复)选按钮状态
;入: uhMenu=菜单句柄
; Id=要改变的菜单项ID
;出: EAX=该菜单的当前状态:
; MFS_CHECKED: 被选中
; 0: 未选中,或不是单(复)选菜单项.
;===========================================
Menu_ChangeCheck proc uhMenu:QWORD,Id:DWORD
LOCAL ss_mi:MENUITEMINFO
;---取菜单的状态与类型---
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_STATE or MIIM_FTYPE
invoke GetMenuItemInfo,uhMenu,Id,NULL,ADDR ss_mi
test ss_mi.fType,MFT_RADIOCHECK
jnz ss_radio
test Id,MENU_CHECKITEM
jz ss_no
;---为复选菜单项(翻转状态位)---
xor ss_mi.fState,MFS_CHECKED
mov ss_mi.fMask,MIIM_STATE
invoke SetMenuItemInfoW,uhMenu,Id,NULL,ADDR ss_mi
mov eax,ss_mi.fState
and eax,MFS_CHECKED
ret
;---为单选菜单项(修改组内各项目状态)---
ss_radio:
mov eax,Id
mov edx,eax
and edx,NOT MENU_RADIO_IIDMK ;组内最小ID-1
and eax,MENU_RADIO_GNMK
mov ecx,MENU_RADIO_GNMK
bsf ecx,ecx
shr eax,cl ;eax=组内项目数
or eax,edx ;组内最大ID
or rdx,1
invoke CheckMenuRadioItem,uhMenu,edx,eax,Id,MF_BYCOMMAND
ss_out:
mov eax,MFS_CHECKED
ret
ss_no: ;不是单(复)选菜单项
xor eax,eax
ret
Menu_ChangeCheck endp
这是一个通用的单(复)选菜单状态处理函数,如果用户点击某一个菜单,则窗口过程会收到WM_COMMAND消息,其中wParam的低16位值为菜单ID,用户无需区分该ID的菜单类型,直接调用Menu_ChangeCheck进行自动处理。
2. 识别菜单ID是否为主菜单
在绘制菜单中,主菜单与子菜单的绘制是有所区别的,但WM_MEASUREITEM消息没有提供菜单句柄参数,只提供了一个菜单ID,所以不能有针对性地确定菜单项绘制尺寸。因此必须编写一个函数,来确定该菜单ID是主菜单项还是子菜单项。如果调用以下函数Menu_isOwnerId,并将主菜单句柄和某一菜单ID传递给函数,就可以确定该菜单ID是否为主菜单ID。
但这样做还有一个小问题,就是菜单的分隔条问题,因为菜单分隔条ID都默认为0,这样就不能使用枚举方法来确定是子菜单的水平分隔条还是主菜单的垂直分隔条。为了解决这个问题,约定将主菜单的分隔条ID设置为0ffffh,将子菜单的分隔条ID设置为0000h,所幸的是分隔条也可以指定ID。
;=============================================
;分析一个菜单ID是否为指定的菜单条
;入: uhMenu=主菜单句柄
; mId=菜单ID
;出: EAX=该菜单ID在菜单条中的序号(0...)
; 如果是分隔条则为第一个分隔条序号。
; =-1: 该菜单ID不属于该菜单。
;-------------------------------------------
;例: 如果uhMenu为主菜单句柄,mId也是主菜单Id,
; 则返回所在的序号;如果mId为子菜单Id,则
; 返回-1。
;=============================================
Menu_isOwnerId proc uhMenu:QWORD,mId:DWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_n:DWORD
LOCAL ss_i:DWORD
invoke GetMenuItemCount,rcx
cmp eax,0
jle ss_no
mov ss_n,eax
mov ss_i,0
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_ID
ss_lp1:
invoke GetMenuItemInfoW,uhMenu,ss_i,TRUE,ADDR ss_mi
mov eax,ss_mi.wID
cmp eax,mId
jz ss_ok
inc ss_i
dec ss_n
jnz ss_lp1
ss_no:
mov eax,-1
ret
ss_ok:
mov eax,ss_i
ret
Menu_isOwnerId endp
3. 绘图对象池
在编程中经常要用到绘图对象,如画笔、画刷、字体、位图、区域、调色板等句柄值,这些句柄统一使用DeleteObject函数释放。有些句柄需要在程序退出时才能删除,如果使用一对一的变量来存放这些句柄是不太方便的,而且容易造成资源遗留问题。使用对象池,将这些句柄都放入对象池中,当窗口过程收到WM_DESTROY消息时统一删除这些句柄,既方便也安全。
以下为一个简单的对象池函数,没有检索功能,只为满足简单的使用。
;-------------------------------------------------------
; 对象池。用于存放由DeleteObject函数释放的绘图对象句柄,
; 包括逻辑画笔、画刷、字体、位图、区域、调色板。
; 对象池的结构如下:
; dd ? ;已保存的对象个数
; dq ?,?.. ;对象
;--------------------------------------------------------
.data
pObj_Pool dq 0 ;对象池地址(须初始化为0)
.code
;=============================================
;将位图、画刷等句柄添加到对象池
;入: hObj=绘图对象句柄
; 使用DeleteObject函数删除的任可句柄。
; =0: 不操作
;出: RAX=hObj: 成功
;注: 程序退出时调用ObjPool_Free删除所全对象。
;=============================================
ObjPool_AddItem proc hObj:QWORD
xor rax,rax
test rcx,rcx ;hObj
jz ss_0
cmp pObj_Pool,0
jnz ss_1
;---创建对象池---
call GetProcessHeap ;获取默认堆句柄
invoke HeapAlloc,rax,0,12
mov pObj_Pool,rax
xor ecx,ecx
mov [rax],ecx ;对象个数置0
jmp ss_2
ss_1:
;---扩展内存---
call GetProcessHeap ;获取默认堆句柄
mov r8,pObj_Pool
mov r9d,[r8] ;已保存的对象个数
inc r9d
shl r9d,3 ;*8
add r9d,4
invoke HeapReAlloc,rax,0,r8,r9
mov pObj_Pool,rax
;---将新的值置入对象池---
ss_2:
mov rcx,pObj_Pool
mov rax,hObj
mov edx,[rcx]
mov [rcx+rdx*8+4],rax
inc edx
mov [rcx],edx ;对象个数增1
ss_0:
ret
ObjPool_AddItem endp
;====================================
;释放对象池中的全部绘图对象
;程序退出时调用
;====================================
ObjPool_Free proc
LOCAL ss_rsi:QWORD
LOCAL ss_n:DWORD
mov ss_rsi,rsi ;保护
mov rsi,pObj_Pool
test rsi,rsi
jz ss_0
lodsd
test eax,eax
jz ss_out
mov ss_n,eax ;对象个数
ss_lp1:
lodsq
invoke DeleteObject,rax
dec ss_n
jnz ss_lp1
ss_out:
call GetProcessHeap ;获取默认堆句柄
invoke HeapFree,rax,0,pObj_Pool
mov pObj_Pool,0
ss_0:
mov rsi,ss_rsi
ret
ObjPool_Free endp
对象池可以作为静态库函数或源码模板函数。如果扩展对象池的概念,将其应用到程序环境的管理上,是很有用的。
4. 根据位图的位数据绘制位图
在绘制菜单或控件时,绘制最多的可能是单(复)选位图,为每一个菜单项创建一个单(复)选位图是很浪费的,当然也可通过共用位图句柄的方法解决这个问题,例如以下为装载系统预定义的复选位图:
;=======================================
;装载系统预定义的复选位图
;返回: RAX=位图句柄
;=======================================
.data
hBmpCheck dq 0 ;初始化值必须为0
.code
LoadCheckBmp proc
mov rax,hBmpCheck
test rax,rax
jnz ss_0
invoke LoadImage,NULL,OBM_CHECK,IMAGE_BITMAP,0,0,LR_SHARED or LR_DEFAULTSIZE
mov hBmpCheck,rax
invoke ObjPool_AddItem,rax ;添加到对象池
mov rax,hBmpCheck
ss_0:
ret
LoadCheckBmp endp
下面介绍SetDIBitsToDevice函数的使用方法,即使用位图的位数据绘制位图,而不是通过位图句柄显示位图。这种方法很适用于小的单色位图。
先编写一个绘图函数CtrlBmp_Draw,该函数可以绘制任何单色位图。单色位图不一定是黑白位图,可以有颜色,颜色由事先设置到hdc环境中的颜色决定。
;========================================================
;绘制控件位图
;入: hdc=目标DC。使用当前设置的颜色。
; pRc=RECT结构地址。包含显示区域坐标。
; pBits=位图数据地址。格式如下:
; dd ? ; 以像素为单位的图像宽度
; dd ? ; 以像素为单位的图像高度
; db ... ; 位图的位值数组(见BITMAPINFO结构说明)
; zFlag=0: 保留
;========================================================
CtrlBmp_Draw proc hdc:QWORD,pRc:QWORD,pBits:QWORD,zFlag:QWORD
LOCAL ss_rbx:QWORD
LOCAL ss_bm[SIZEOF BITMAPINFOHEADER +RGBQUAD*2]:BYTE
LOCAL ss_bwt:DWORD
LOCAL ss_bht:DWORD
mov ss_rbx,rbx
lea rbx,ss_bm ;单色位图需要2个RGBQUAD结构
;---填写BITMAPINFOHEADER结构---
mov [rbx.BITMAPINFOHEADER].biSize,SIZEOF BITMAPINFOHEADER
mov eax,[r8] ;pBits
mov ss_bwt,eax ;位图宽度
mov [rbx.BITMAPINFOHEADER].biWidth,eax
mov eax,[r8+4]
mov ss_bht,eax ;位图高度
neg eax ;位图扫描行从上到下,使用负值
mov [rbx.BITMAPINFOHEADER].biHeight,eax ;以像素为单位的图像高度。
mov [rbx.BITMAPINFOHEADER].biPlanes,1 ;目标设备的位平面数
mov [rbx.BITMAPINFOHEADER].biBitCount,1 ;每个像素的位数。
mov [rbx.BITMAPINFOHEADER].biCompression,0 ;默认
;---计算位图数据字节长度---
mov eax,ss_bwt
shr eax,3 ;/8=行字节长度
add eax,3h ;31
and eax,NOT 3h ;每行4字节对齐
mul ss_bht
mov [rbx.BITMAPINFOHEADER].biSizeImage,eax ;以字节为单位的图像数据的大小
mov [rbx.BITMAPINFOHEADER].biXPelsPerMeter,0 ;默认
mov [rbx.BITMAPINFOHEADER].biYPelsPerMeter,0 ;默认
mov [rbx.BITMAPINFOHEADER].biClrUsed,0 ;默认
mov [rbx.BITMAPINFOHEADER].biClrImportant,0 ;默认
add rbx,SIZEOF BITMAPINFOHEADER ;指向RGB颜色表
;---填写RGB颜色表---
invoke GetTextColor,hdc ;取当前的前景色
;---转换为RGB---
rol ax,8
rol eax,16
rol ax,8
ror eax,8
mov [rbx],eax ;第1个RGBQUAD结构存放前景色
invoke GetBkColor,hdc ;取当前的背景色
rol ax,8
rol eax,16
rol ax,8
ror eax,8
mov [rbx+4],eax ;第2个RGBQUAD结构存放背景色
add pBits,8 ;指向位图的位数据
;---计算显示居中坐标---
mov rax,pRc
mov edx,[rax.RECT].right
sub edx,[rax.RECT].left
sub edx,ss_bwt
sar edx,1
add edx,[rax.RECT].left ;水平居中
mov r8d,[rax.RECT].bottom
sub r8d,[rax.RECT].top
sub r8d,ss_bht
sar r8d,1
add r8d,[rax.RECT].top ;垂直居中
invoke SetDIBitsToDevice,hdc,edx,r8d,ss_bwt,ss_bht,0,0,0,ss_bht,pBits,\
ADDR ss_bm,DIB_RGB_COLORS
mov rbx,ss_rbx
ret
CtrlBmp_Draw endp
下面定义一个复选位图的位数据。位图的位数据每行必须为4字节对齐,如宽度为16位的位图,每行2个字节,但后面须补填2字节,这补填的2字节可填入任意值。
;----------------------------------------
;复选位图数据。每行必须为4字节对齐。
;位值="1"为背景色。对应黑白位图的白底。
; ="0"为前景色。对应黑白位图的图形。
;----------------------------------------
Check_data dd 16,16 ;位图的宽和高(这是自定义参数)
db 0ffh,0ffh,0,0 ;第1行扫描行位值
db 0ffh,0ffh,0,0 ;第2...
db 0ffh,0ffh,0,0
db 0ffh,0ffh,0,0
db 0ffh,0ffh,0,0
db 0ffh,0efh,0,0
db 0ffh,0cfh,0,0
db 0ffh,08fh,0,0
db 0f7h,01fh,0,0
db 0f2h,03fh,0,0
db 0f0h,07fh,0,0
db 0f8h,0ffh,0,0
db 0fdh,0ffh,0,0
db 0ffh,0ffh,0,0
db 0ffh,0ffh,0,0
db 0ffh,0ffh,0,0
下面是使用复选位图的位数据绘制复选位图的函数。
;===========================================
;绘制控件的复选位图
;入: hdc=目标DC
; pRc=RECT结构地址。包含显示区域坐标。
; zFlag=0: 保留
;===========================================
CtrlBmp_DrawCheck proc hdc:QWORD,pRc:QWORD,\
zFlag:DWORD
invoke CtrlBmp_Draw,rcx,rdx,ADDR Check_data
ret
CtrlBmp_DrawCheck endp
对于单选位图,也可用上述方法绘制。但因为单选位图是一个规则圆,直接使用椭圆函数Ellipse来绘制更利于控制。单选位图绘制函数如下。
;=============================================
;绘制控件的单选位图
;入: hdc=目标DC
; pRc=RECT结构地址。包含显示区域坐标。
; zFlag=0: 保留
;=============================================
CtrlBmp_DrawRadio proc hdc:QWORD,pRc:QWORD,\
zFlag:DWORD
LOCAL ss_hOldBrush:QWORD
LOCAL ss_hOldPen:QWORD
LOCAL ss_color:DWORD
invoke GetTextColor,hdc ;取当前的前景色
mov ss_color,eax
invoke CreateSolidBrush,rax
invoke SelectObject,hdc,rax
mov ss_hOldBrush,rax
invoke CreatePen,0,1,ss_color
invoke SelectObject,hdc,rax
mov ss_hOldPen,rax
mov rax,pRc
mov edx,[rax.RECT].right
sub edx,[rax.RECT].left
sub edx,5 ;5=圆直径
shr rdx,1
add edx,[rax.RECT].left ;x
mov r8d,[rax.RECT].bottom
sub r8d,[rax.RECT].top
sub r8d,5 ;5=圆直径
shr r8d,1
add r8d,[rax.RECT].top ;y
mov r9d,edx
add r9d,5
mov r10d,r8d
add r10d,5
invoke Ellipse,hdc,edx,r8d,r9d,r10d
invoke SelectObject,hdc,ss_hOldBrush
invoke DeleteObject,rax
invoke SelectObject,hdc,ss_hOldPen
invoke DeleteObject,rax
ret
CtrlBmp_DrawRadio endp
5. 菜单项绘制的初始化消息
当一个菜单项被设置了MFT_OWNERDRAW志标,系统就会向窗口过程发送初始化消息WM_MEASUREITEM消息,要求用户填写指定菜单项的相关尺寸,对每一个菜单项只发送一次。如果是主菜单,则立即发送;如果是子菜单,则在子菜单打开时发送。
WM_MEASUREITEM消息的lParam参数是一个MEASUREITEMSTRUCT结构。该结构并不是菜单绘制专用,也用于其它控件。以下针对菜单应用中相关参数进行说明:
MEASUREITEMSTRUCT STRUCT QWORD
CtlType DWORD ?
CtlID DWORD ?
itemID DWORD ?
itemWidth DWORD ?
itemHeight DWORD ?
itemData QWORD ?
MEASUREITEMSTRUCT ENDS
(1) CtlType=如果是菜单项,则该值必须ODT_MENU,否则与菜单无关。
(2) CtlID=控件ID。绘制菜单时不使用该值。
(3) itemID=菜单项ID。对于菜单分隔线或子菜单标题项也有ID值。
(4) itemWidth=要求用户填写的菜单项的文本绘制宽度(以像素为单位)。该宽度值不包括菜单项的位图宽度,系统会自动处理位图宽度。
(5) itemHeight=要求用户填写的菜单项的文本绘制高度(以像素为单位)。对于主菜单项,则不需要填写该成员,系统采用默认高度。
注意:对于主菜单,如果设置了hbmpItem位图,并且位图的高度超过了主菜条的默认高度,则整个主菜单条的高度将自动调整为该位图的高度。
(6) itemData=这是菜单项中用户所关联的自定义值,即MENUITEMINFO结构中的dwItemData成员值。如果想对每一个菜单项实现针对性的尺寸控制,则可以通过它来传递用户自定义结构,提供专用的字体或尺寸参数。
注意,如果某一菜单项使用了hbmpItem位图,则系统不会发送WM_MEASUREITEM消息,这样就不能准确地填写itemWidth和itemHeight成员。因此,如果自己绘制菜单,并通过自动计算来确定文本绘制尺寸,则不要将hbmpItem位图与文本同时用于一个菜单项中。如果文本绘制尺寸采用固定值,则就不存在该问题了。
这里顺便提一下,如果在资源中定义一个无文本的菜单项,则将文本串定义为"\nul":
MENUITEM "\nul", IDM_LOCK
不能使用为"\0"或"",这样设置就成为一个分隔条了。这是在Masm64编程环境下的情况。
以下为处理WM_MEASUREITEM消息,并自动计算菜单项尺寸参数的源码。为了使菜单绘制整齐美观,定义了3个常量,其中MENU_DEFWT和MENU_DEFHT是为了确保子菜单尽量有统一的尺寸;MENU_DEFBMPWT用于扩大菜单项左边的位图区域,因为系统默认提供的区域只有17,视觉上感到比较拥挤。
MENU_DEFWT EQU 124 ;子菜单默认宽度
MENU_DEFHT EQU 22 ;子菜单默认高度
MENU_DEFBMPWT EQU 40 ;子菜单左边默认位图区宽度
;========================================================
;设置自绘项目的尺寸---在WM_MEASUREITEM消息处调用
;入: hWin=窗口句柄
; uhMenu=窗口主菜单句柄
; hfont=绘制菜单所用的字体句柄
; =0: 系统默认
; lParam=WM_MEASUREITEM消息参数
;出: EAX=TRUE: 消息已处理
; =NULL: 不是菜单所属消息
; 位图尺寸系统自动处理,不需要考虑。
;========================================================
Menu_SetItemSize proc hWin:QWORD,uhMenu:QWORD,\
hfont:QWORD,lParam:QWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_buf[MAX_PATH+1]:WORD
LOCAL ss_size:SIZEL
LOCAL ss_hdc:QWORD
LOCAL ss_oldfont:QWORD
mov ss_size.x,0
xor rax,rax
cmp [r9.MEASUREITEMSTRUCT].CtlType,ODT_MENU
jnz ss_0 ;不是菜单绘制
mov eax,[r9.MEASUREITEMSTRUCT].itemID
test eax,eax
jz ss_hsp ;水平分隔条
cmp ax,-1
jz ss_vsp ;垂直分隔条
;---取菜单标题---
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_STRING
lea rax,ss_buf
mov ss_mi.dwTypeData,rax
xor cx,cx
mov [rax],cx
mov ss_mi.cch,MAX_PATH
mov edx,[r9.MEASUREITEMSTRUCT].itemID ;菜单ID
invoke GetMenuItemInfoW,uhMenu,edx,0,ADDR ss_mi
test eax,eax
jz ss_2 ;这种情况一般为独立的快捷菜单
cmp ss_mi.cch,0
jz ss_2 ;无标题字符串
;---计算菜单标题文本所需绘制宽度(ss_size.x为返回值)---
invoke GetDC,hWnd ;默认DC
mov ss_hdc,rax
mov rax,hfont
test rax,rax
jnz ss_3
invoke GetStockObject,SYSTEM_FONT ;取菜单默认字体
ss_3:
invoke SelectObject,ss_hdc,rax
mov ss_oldfont,rax
invoke GetTextExtentPoint32W,ss_hdc,ADDR ss_buf,ss_mi.cch,ADDR ss_size
;--恢复DC环境--
invoke SelectObject,ss_hdc,ss_oldfont
invoke ReleaseDC,hWnd,ss_hdc
ss_2:
mov r9,lParam
mov eax,ss_size.y ;文本绘制高度
cmp rax,MENU_DEFHT
jnc ss_5
mov eax,MENU_DEFHT
ss_5:
mov [r9.MEASUREITEMSTRUCT].itemHeight,eax ;默认高度
;---是否为子菜单项目---
mov edx,[r9.MEASUREITEMSTRUCT].itemID
invoke Menu_isOwnerId,uhMenu,edx
mov r9,lParam
mov ecx,ss_size.x ;文本绘制宽度
cmp eax,-1
jnz ss_ok ;为主菜单项目
;---是子菜单项目---
add ecx,MENU_DEFBMPWT
cmp ecx,MENU_DEFWT
jnc ss_ok
mov ecx,MENU_DEFWT ;取默认宽度
ss_ok:
mov [r9.MEASUREITEMSTRUCT].itemWidth,ecx
mov rax,TRUE
jmp ss_0
ss_vsp: ;垂直分隔条
mov [r9.MEASUREITEMSTRUCT].itemWidth,5
jmp ss_0
ss_hsp: ;水平分隔条
mov [r9.MEASUREITEMSTRUCT].itemHeight,9 ;水平分隔线高度
mov [r9.MEASUREITEMSTRUCT].itemWidth,MENU_DEFWT
ss_0:
ret
Menu_SetItemSize endp
6. 菜单栏的背景色
菜单栏的背景色可以使用SetMenuInfo函数设置。该函数还可设置菜单栏的其它一此信息,使用比较简单,具体可查阅Windows文档。
通常情况下将菜单栏的背景色设置为窗口背景色,这样整体会比较谐调。如果不设置菜单栏背景色,则默认为控件背景色。
;=================================================
;设置菜单条的背景色
;入:hWin=窗口句柄
; =0: 不重绘
; uhMenu=菜单句柄
; bkColor=背景色
; zFlag=0(保留)
;出: NO
;=================================================
Menu_SetBackColor proc hWin:QWORD,uhMenu:QWORD,\
bkColor:DWORD,zFlag:DWORD
LOCAL ss_mu:MENUINFO
mov ss_mu.cbSize,SIZEOF MENUINFO
mov ss_mu.fMask,MIM_BACKGROUND or MIM_APPLYTOSUBMENUS
invoke CreateSolidBrush,bkColor ;创建背景画刷句柄
mov ss_mu.hbrBack,rax
invoke ObjPool_AddItem,rax ;投入对象池
invoke SetMenuInfo,uhMenu,ADDR ss_mu
cmp hWin,0
jz ss_0
invoke DrawMenuBar,hWin
ss_0:
ret
Menu_SetBackColor endp
7. 菜单绘制函数
菜单项的绘制包括3个项目,即菜单位图、菜单文本和分隔条。以下共4个函数来完成绘制任务:
(1)Menu_DrawItem :主函数,处理绘制的DC环境。
(2)Menu_DrawBmp :绘制菜单位图。
(3)Menu_DrawTxt :绘制菜单文本。
(4)Menu_DrawSeparator:绘制菜单分隔条。
;=====================================================
;绘制菜单项位图
;入: pDrawItem=DRAWITEMSTRUCT结构地址
; pMenuInfo=MENUITEMINFO结构地址
; zFlag=0: 保留
;返回: EAX=位图绘制区的宽度
; =0: 无位图
;=====================================================
Menu_DrawBmp proc pDrawItem:QWORD,pMenuInfo:QWORD,\
zFlag:DWORD
LOCAL ss_rc:RECT
LOCAL ss_bitmap:BITMAP
LOCAL ss_hBmp:QWORD
LOCAL ss_hTempDC:QWORD
LOCAL ss_wt:DWORD
mov ss_wt,MENU_DEFBMPWT ;位图绘制区的最小宽度
;---输出区域---
mov eax,[rcx.DRAWITEMSTRUCT].rcItem.left
mov ss_rc.left,eax
mov eax,[rcx.DRAWITEMSTRUCT].rcItem.top
mov ss_rc.top,eax
mov eax,[rcx.DRAWITEMSTRUCT].rcItem.bottom
mov ss_rc.bottom,eax
mov eax,[rcx.DRAWITEMSTRUCT].rcItem.right
cmp [rdx.MENUITEMINFO].cch,0
jz ss_2 ;无菜单标题字符串
mov r10,[rdx.MENUITEMINFO].dwTypeData
mov r10w,[r10]
cmp r10w,20h
jc ss_2 ;首个字符为不可见,作空串处理
mov eax,ss_wt
add eax,ss_rc.left
ss_2:
mov ss_rc.right,eax
;---确定要绘制的位图---
mov rax,[rdx.MENUITEMINFO].hbmpItem
test rax,rax
jnz ss_3
mov rax,[rdx.MENUITEMINFO].hbmpChecked
test [rdx.MENUITEMINFO].fState,MFS_CHECKED
jnz ss_3
mov rax,[rdx.MENUITEMINFO].hbmpUnchecked
ss_3:
test rax,rax
jnz ss_bmp
;---无位图时绘制单(复)选位图---
xor eax,eax
test [rdx.MENUITEMINFO].fState,MFS_CHECKED
jz ss_0
test [rdx.MENUITEMINFO].fType,MFT_RADIOCHECK
jz ss_Check
invoke CtrlBmp_DrawRadio,QWORD PTR [rcx.DRAWITEMSTRUCT].hdc,ADDR ss_rc,0
mov eax,ss_wt
jmp ss_0
ss_Check:
invoke CtrlBmp_DrawCheck,QWORD PTR [rcx.DRAWITEMSTRUCT].hdc,ADDR ss_rc,0
mov eax,ss_wt
jmp ss_0
ss_bmp:
mov ss_hBmp,rax
;---计算位图居中坐标---
invoke GetObject,ss_hBmp,SIZEOF BITMAP,ADDR ss_bitmap ;获取位图尺寸
mov eax,ss_rc.bottom
sub eax,ss_rc.top
sub eax,ss_bitmap.bmHeight
sar eax,1
add ss_rc.top,eax ;垂直居中
mov eax,ss_rc.right
sub eax,ss_rc.left
sub eax,ss_bitmap.bmWidth
jl ss_5
sar eax,1
add ss_rc.left,eax ;水平居中
ss_5:
;---显示位图(使用TransparentBlt函数实现背景透明)---
invoke CreateCompatibleDC,NULL
mov ss_hTempDC,rax
invoke SelectObject,rax,ss_hBmp
mov rcx,pDrawItem
mov rcx,[rcx.DRAWITEMSTRUCT].hdc
invoke TransparentBlt,rcx,ss_rc.left,ss_rc.top,\
ss_bitmap.bmWidth,ss_bitmap.bmHeight,\
ss_hTempDC,0,0,ss_bitmap.bmWidth,\
ss_bitmap.bmHeight,0ffffffh
invoke DeleteDC,ss_hTempDC
mov eax,ss_bitmap.bmWidth
cmp eax,ss_wt
jnbe ss_0
mov eax,ss_wt
ss_0:
ret
Menu_DrawBmp endp
;===================================================
;绘制菜单项标题字符串
;入: pDrawItem=DRAWITEMSTRUCT结构地址
; pMenuInfo=MENUITEMINFO结构地址
; bwt=位图区宽度
; zFlag.p0=0: 子菜单项目
; =1: 主菜单项目
;===================================================
Menu_DrawTxt proc pDrawItem:QWORD,pMenuInfo:QWORD,\
bwt:DWORD,zFlag:DWORD
LOCAL ss_x:DWORD
mov eax,[rcx.DRAWITEMSTRUCT].rcItem.left
mov ss_x,eax ;保护
test zFlag,1
jnz ss_1
;---是子菜单项---
cmp r8d,MENU_DEFBMPWT
jnc ss_1
mov r8d,MENU_DEFBMPWT ;子菜单位图绘制区的最小宽度
ss_1:
add eax,r8d ;bwt
mov [rcx.DRAWITEMSTRUCT].rcItem.left,eax
mov rdx,[rdx.MENUITEMINFO].dwTypeData ;文本地址
test rdx,rdx
jz ss_0
mov ax,[rdx]
cmp ax,20h
jc ss_0 ;首个字符为不可见,作空串处理
mov r10d,DT_VCENTER or DT_SINGLELINE or DT_CENTER ;文本绘制格式
test zFlag,1
jnz ss_2
mov r10d,DT_VCENTER or DT_SINGLELINE
ss_2:
lea r9,[rcx.DRAWITEMSTRUCT].rcItem
mov rcx,[rcx.DRAWITEMSTRUCT].hdc
invoke DrawTextW,rcx,rdx,-1,r9,r10d
;---恢复区域参数---
ss_0:
mov rcx,pDrawItem
mov eax,ss_x
mov [rcx.DRAWITEMSTRUCT].rcItem.left,eax
ret
Menu_DrawTxt endp
;===================================================
;绘制菜单项分隔条
;入: pDrawItem=DRAWITEMSTRUCT结构地址
; zFlag.p0=0: 子菜单项目
; =1: 主菜单项目
;===================================================
Menu_DrawSeparator proc pDrawItem:QWORD,zFlag:DWORD
test edx,1
jnz ss_vert
;---水平分隔条---
lea rdx,[rcx.DRAWITEMSTRUCT].rcItem
mov eax,[rdx.RECT].bottom
sub eax,[rdx.RECT].top
shr eax,1
add [rdx.RECT].top,eax
mov rcx,[rcx.DRAWITEMSTRUCT].hdc
invoke DrawEdge,rcx,rdx,EDGE_ETCHED,BF_TOP
ret
;---垂直分隔条---
ss_vert:
lea rdx,[rcx.DRAWITEMSTRUCT].rcItem
mov eax,[rdx.RECT].right
sub eax,[rdx.RECT].left
shr eax,1
add [rdx.RECT].left,eax
add [rdx.RECT].top,2
sub [rdx.RECT].bottom,2
mov rcx,[rcx.DRAWITEMSTRUCT].hdc
invoke DrawEdge,rcx,rdx,EDGE_ETCHED,BF_LEFT
ret
Menu_DrawSeparator endp
;==============================================
;绘制菜单项目---在WM_DRAWITEM消息处调用
;入: hWin=窗口句柄
; uhMenu=窗口主菜单句柄
; hfont=绘制菜单所用的字体句柄
; =0: 系统默认
; lParam=DRAWITEMSTRUCT消息参数
;==============================================
Menu_DrawItem proc hWin:QWORD,uhMenu:QWORD,\
hfont:QWORD,lParam:QWORD
LOCAL ss_rbx:QWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_buf[MAX_PATH+1]:WORD
LOCAL ss_OldhFont:QWORD
LOCAL ss_OldTxColor:DWORD
LOCAL ss_OldBkColor:DWORD
LOCAL ss_OldMode:DWORD
LOCAL ss_txColor:DWORD
LOCAL ss_bkColor:DWORD
LOCAL ss_hBkBrush:QWORD
LOCAL ss_hPen:QWORD
LOCAL ss_Flag:DWORD
LOCAL ss_hdc:QWORD
mov ss_rbx,rbx
mov rbx,r9 ;DRAWITEMSTRUCT
mov rax,[rbx.DRAWITEMSTRUCT].hdc
mov ss_hdc,rax
;---是否为主菜单项目---
mov ss_Flag,0
cmp rdx,[rbx.DRAWITEMSTRUCT].hwndItem
jnz ss_1
or ss_Flag,1 ;为主菜单项目
ss_1:
;---取菜单信息---
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_STRING or MIIM_STATE or MIIM_CHECKMARKS \
or MIIM_BITMAP or MIIM_FTYPE or MIIM_DATA
lea rax,ss_buf
mov ss_mi.dwTypeData,rax
xor cx,cx
mov [rax],cx
mov ss_mi.cch,MAX_PATH
mov rcx,[rbx.DRAWITEMSTRUCT].hwndItem ;菜单句柄
mov edx,[rbx.DRAWITEMSTRUCT].itemID ;菜单ID
invoke GetMenuItemInfoW,rcx,edx,0,ADDR ss_mi
test ss_mi.fType,MFT_SEPARATOR
jz SS_2
invoke Menu_DrawSeparator,rbx,ss_Flag
jmp ss_0
SS_2:
;------------------
;确定颜色
;------------------
test [rbx.DRAWITEMSTRUCT].itemState,(ODS_GRAYED or ODS_DISABLED)
jnz ss_gray
test [rbx.DRAWITEMSTRUCT].itemState,ODS_SELECTED or ODS_FOCUS or ODS_HOTLIGHT
jnz ss_hot
;---非选择状态---
mov ss_txColor,MENU_TXCOLOR
mov ss_bkColor,MENU_BKCOLOR
jmp ss_setcolor
ss_hot:
;---热点状态---
mov ss_txColor,MENU_HTXCOLOR
mov ss_bkColor,MENU_HBKCOLOR
jmp ss_setcolor
ss_gray:
;---禁用项目---
mov ss_txColor,MENU_UTXCOLOR
mov ss_bkColor,MENU_BKCOLOR
;---充填背景色和文本色---
ss_setcolor:
invoke CreateSolidBrush,ss_bkColor
mov ss_hBkBrush,rax
invoke FillRect,ss_hdc,ADDR [rbx.DRAWITEMSTRUCT].rcItem,ss_hBkBrush;
invoke SetTextColor,ss_hdc,ss_txColor
mov ss_OldTxColor,eax
invoke SetBkColor,ss_hdc,ss_bkColor
mov ss_OldBkColor,eax
invoke SetBkMode,ss_hdc,TRANSPARENT
mov ss_OldMode,eax
;---设置字体---
cmp hfont,0
jz ss_3
invoke SelectObject,ss_hdc,hfont
mov ss_OldhFont,rax
ss_3:
;---绘制位图---
invoke Menu_DrawBmp,rbx,ADDR ss_mi,0
;---绘制文本---
mov r8d,eax
invoke Menu_DrawTxt,rbx,ADDR ss_mi,r8d,ss_Flag
;---绘制热点矩形---
test [rbx.DRAWITEMSTRUCT].itemState,ODS_SELECTED or ODS_FOCUS or ODS_HOTLIGHT
jz ss_out
test [rbx.DRAWITEMSTRUCT].itemState,(ODS_GRAYED or ODS_DISABLED)
jnz ss_out
invoke CreatePen,0,1,MENU_FOCUSCOLOR
mov ss_hPen,rax
invoke FrameRect,ss_hdc,ADDR [rbx.DRAWITEMSTRUCT].rcItem,ss_hPen
invoke DeleteObject,ss_hPen
ss_out:
invoke DeleteObject,ss_hBkBrush
;---恢复DC环境---
invoke SetTextColor,ss_hdc,ss_OldTxColor
invoke SetTextColor,ss_hdc,ss_OldBkColor
invoke SetBkMode,ss_hdc,ss_OldMode
cmp hfont,0
jz ss_0
invoke SelectObject,ss_hdc,ss_OldhFont
ss_0:
mov rbx,ss_rbx
ret
Menu_DrawItem endp
8. 实例
为了演示菜单项各要素与状态的绘制情况,本实例中的菜单定义了2组单选菜单项目、2个复选菜单项目、2个hbmpItem位图主菜单、2个hbmpItem位图子菜单、1个禁用项目、1条垂直分隔线和3条水平分隔线。
8.1 资源菜单
以下为菜单资源文件的内容。菜单定义中并没有指定绘制志标MFT_OWNERDRAW,由用户装入菜单后再进行设置。只所以不在资源文件中指定,是为了演示的需要,即便于观察绘制与不绘制的区别。
//菜单位图
700 BITMAP Image\File.bmp
701 BITMAP Image\New.bmp
702 BITMAP Image\Save.bmp
703 BITMAP Image\SaveAs.bmp
704 BITMAP Image\Exit.bmp
710 BITMAP Image\Edit.bmp
711 BITMAP Image\Cut.bmp
712 BITMAP Image\Copy.bmp
713 BITMAP Image\Paste.bmp
714 BITMAP Image\Delete.bmp
715 BITMAP Image\Lock.bmp
716 BITMAP Image\UnLock.bmp
721 BITMAP Image\Help.bmp
722 BITMAP Image\About.bmp
//菜单ID
#define IDM_MAIN 10 //主菜单条ID
#define IDM_FILE 0100 //文件子菜单
#define IDM_NEW 0101
#define IDM_SAVE 0102
#define IDM_SAVEAS 0103
#define IDM_EXIT 0104
#define IDM_EDIT 0200 //编辑子菜单
#define IDM_CUT 0201
#define IDM_COPY 0202
#define IDM_PASTE 0203
#define IDM_DEL 0204
#define IDM_LOCK 0x8205 //这是复选菜单ID
#define IDM_LOOK 0300 //查看子菜单
#define IDM_WRAP 0x8301 //这是复选菜单ID
#define IDM_SIMPLE 0x1301 //由3个菜单组成的一组单选菜单
#define IDM_TRAD 0x1302
#define IDM_ENGLISH 0x1303
#define IDM_ALIGN 0400 //对齐子菜单
#define IDM_LEFT 0x2401 //由4个菜单组成的一组单选菜单
#define IDM_TOP 0x2402
#define IDM_DEFAULT 0x2403
#define IDM_SNAP 0x2404
#define IDM_THELP 500 //帮助子菜单
#define IDM_HELP 501
#define IDM_ABOUT 502
IDM_MAIN MENUEX // 定义主菜单条
BEGIN
POPUP "\nul",IDM_FILE,,,01
BEGIN
MENUITEM "新 建", IDM_NEW
MENUITEM "保 存", IDM_SAVE
MENUITEM "另存为", IDM_SAVEAS
MENUITEM "退 出", IDM_EXIT
END
POPUP "\nul",IDM_EDIT,,,02 //helpID值
{
MENUITEM "Cut", IDM_CUT
MENUITEM "Copy", IDM_COPY
MENUITEM "Paste", IDM_PASTE
MENUITEM "", , MFT_SEPARATOR
MENUITEM "Delete", IDM_DEL
MENUITEM "", , MFT_SEPARATOR
MENUITEM "Lock", IDM_LOCK,,MFS_CHECKED
}
POPUP "查 看",IDM_LOOK,,,03 //helpID值
{
MENUITEM "自动换行", IDM_WRAP ,,MFS_CHECKED
MENUITEM "", , MFT_SEPARATOR
MENUITEM "简体字", IDM_SIMPLE,MFT_RADIOCHECK,MFS_CHECKED
MENUITEM "繁体字", IDM_TRAD,MFT_RADIOCHECK
MENUITEM "英 文", IDM_ENGLISH,MFT_RADIOCHECK
}
POPUP "对 齐",IDM_ALIGN,,,04 //helpID值
{
MENUITEM "横向对齐", IDM_LEFT ,MFT_RADIOCHECK,MFS_CHECKED
MENUITEM "纵向对齐", IDM_TOP,MFT_RADIOCHECK
MENUITEM "默认对齐", IDM_DEFAULT,MFT_RADIOCHECK
MENUITEM "网格对齐", IDM_SNAP,MFT_RADIOCHECK,MFS_GRAYED
}
MENUITEM "", 0xffff, MFT_SEPARATOR
POPUP "帮 助",IDM_THELP,,,05 //helpID值
BEGIN
MENUITEM "\nul", IDM_HELP
MENUITEM "\nul",IDM_ABOUT
END
END
8.2 菜单装载与位图设置
以下共有5个函数:
Load_RsrcMenu: 菜单装载处理主函数。
Menu_LoadBmp: hbmpItem位图装载。
Menu_LoadCheckBmp: hbmpChecked位图装载。
Menu_SetAllDraw: 将全部菜单项设置MFT_OWNERDRAW志标。
Menu_NewFont: 创建菜单字体。
;---菜单位图ID---
IDB_FILE EQU 700
IDB_NEW EQU 701
IDB_SAVE EQU 702
IDB_SAVEAS EQU 703
IDB_EXIT EQU 704
IDB_EDIT EQU 710
IDB_CUT EQU 711
IDB_COPY EQU 712
IDB_PASTE EQU 713
IDB_DEL EQU 714
IDB_LOCK EQU 715
IDB_UNLOCK EQU 716
IDB_HELP EQU 721
IDB_ABOUT EQU 722
;--------------------------------------------------
;菜单ID志标: 单选组组内首个菜单的组内序号必须为01h。
;--------------------------------------------------
MENU_CHECKITEM EQU 8000h ;复选菜单项志标位
MENU_RADIO_GIDMK EQU 0f000h ;单选组组号掩码(0-f)
MENU_RADIO_GNMK EQU 0f00h ;单选组组内项目数掩码(0-f)
MENU_RADIO_IIDMK EQU 0ffh ;单选组组内序号掩码(1-ff)
;--------------------------------------------------
;---菜单ID---
IDM_MAIN EQU 10 ;主菜单条ID
IDM_FILE EQU 0100 ;文件子菜单
IDM_NEW EQU 0101
IDM_SAVE EQU 0102
IDM_SAVEAS EQU 0103
IDM_EXIT EQU 0104
IDM_EDIT EQU 0200 ;编辑子菜单
IDM_CUT EQU 0201
IDM_COPY EQU 0202
IDM_PASTE EQU 0203
IDM_DEL EQU 0204
IDM_LOCK EQU 8205h ;复选菜单ID
IDM_LOOK EQU 0300 ;查看子菜单
IDM_WRAP EQU 8301h ;复选菜单ID
IDM_SIMPLE EQU 1301h ;第1组单选菜单ID(1=组别,3=项目数,01=起始序号)
IDM_TRAD EQU 1302h ;第1组单选菜单ID
IDM_ENGLISH EQU 1303h ;第1组单选菜单ID
IDM_ALIGN EQU 0400 ;对齐子菜单
IDM_LEFT EQU 2401h ;第2组单选菜单ID
IDM_TOP EQU 2402h ;第2组单选菜单ID
IDM_DEFAULT EQU 2403h ;第2组单选菜单ID
IDM_SNAP EQU 2404h ;第2组单选菜单ID
IDM_THELP EQU 0500 ;帮助子菜单
IDM_HELP EQU 0501
IDM_ABOUT EQU 0502
.data?
hMenuFont dq ? ;菜单字体句柄
hMenu dq ? ;主菜单句柄
.code
;=============================================
;创建菜单字体
;入: pFontName=字体名(UTF-8码)
; fht=字体高度
; =0: 默认
; fWeight=字体粗细度(见LOGFONT结构)。常用:
; 0: 默认
; FW_BOLD: 粗体
;出: RAX=字体句柄
;注: 字体句柄已添加到对象池。
;=============================================
Menu_NewFont proc pFontName:QWORD,fht:DWORD,\
fWeight:DWORD
LOCAL ss_lf:LOGFONTW
invoke RtlZeroMemory,ADDR ss_lf,SIZEOF ss_lf
mov eax,fht
mov ss_lf.lfHeight,eax
mov eax,fWeight
mov ss_lf.lfWeight,eax
invoke MultiByteToWideChar,CP_UTF8,0,pFontName,-1,ADDR ss_lf.lfFaceName,LF_FACESIZE
invoke CreateFontIndirectW,ADDR ss_lf
invoke ObjPool_AddItem,rax ;添加到对象池
ret
Menu_NewFont endp
;===============================================
;将菜单的全部项目(包括子菜单)都设置为自绘格式
;入: uhMenu=菜单句柄
;===============================================
Menu_SetAllDraw proc uhMenu:QWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_i:DWORD
LOCAL ss_num:DWORD
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_ID or MIIM_SUBMENU or MIIM_FTYPE
mov ss_i,0
invoke GetMenuItemCount,uhMenu
cmp eax,0
jle ss_out
mov ss_num,eax
ss_lp1:
invoke GetMenuItemInfoW,uhMenu,ss_i,TRUE,ADDR ss_mi
or ss_mi.fType,MFT_OWNERDRAW
invoke SetMenuItemInfoW,uhMenu,ss_i,TRUE,ADDR ss_mi
cmp ss_mi.hSubMenu,0
jz ss_nt
invoke Menu_SetAllDraw,ss_mi.hSubMenu ;递归
ss_nt:
inc ss_i
dec ss_num
jnz ss_lp1
ss_out:
ret
Menu_SetAllDraw endp
;==============================================================
;装载菜单状态位图,并关联到指定的菜单项
;入: uhMenu=菜单句柄
; mId=位图要关联的菜单ID
; CheckedId=选择状态的位图ID
; =0: 无
; UncheckedId=未选择状态的位图ID
; =0: 无
; =CheckedId: 状态位图不改变
;出: RAX=CheckedId位图句柄
;==============================================================
Menu_LoadCheckBmp proc uhMenu:QWORD,mId:DWORD,\
CheckedId:DWORD,UncheckedId:DWORD
LOCAL ss_hInst:QWORD
LOCAL ss_mi:MENUITEMINFO
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,0
mov ss_mi.hbmpUnchecked,0
mov ss_mi.hbmpChecked,0
invoke GetModuleHandle,0 ;程序实例句柄
mov ss_hInst,rax
cmp CheckedId,0
jz ss_2
invoke LoadImage,ss_hInst,CheckedId,IMAGE_BITMAP,0,0,LR_SHARED or LR_DEFAULTSIZE
mov ss_mi.hbmpChecked,rax
invoke ObjPool_AddItem,rax ;添加到对象池,便于程序退出时释放
or ss_mi.fMask,MIIM_CHECKMARKS
ss_2:
mov edx,UncheckedId
test edx,edx
jz ss_4
mov rax,ss_mi.hbmpChecked
cmp edx,CheckedId
jz ss_3
invoke LoadImage,ss_hInst,UncheckedId,IMAGE_BITMAP,0,0,LR_SHARED or LR_DEFAULTSIZE
invoke ObjPool_AddItem,rax
or ss_mi.fMask,MIIM_CHECKMARKS
ss_3:
mov ss_mi.hbmpUnchecked,rax
ss_4:
invoke SetMenuItemInfoW,uhMenu,mId,NULL,ADDR ss_mi
mov rax,ss_mi.hbmpChecked
ret
Menu_LoadCheckBmp endp
;===========================================
;装载菜单无状态位图,并关联到指定的菜单项
;入: uhMenu=菜单句柄
; mId=位图要关联的菜单ID
; BmpId=位图ID
;出: RAX=位图句柄
;===========================================
Menu_LoadBmp proc uhMenu:QWORD,mId:DWORD,\
BmpId:DWORD
LOCAL ss_hInst:QWORD
LOCAL ss_mi:MENUITEMINFO
LOCAL ss_buf[MAX_PATH+1]:WORD
mov ss_mi.cbSize,SIZEOF MENUITEMINFO
mov ss_mi.fMask,MIIM_BITMAP or MIIM_STRING
lea rax,ss_buf
mov ss_mi.dwTypeData,rax
xor cx,cx
mov [rax],cx
mov ss_mi.cch,MAX_PATH
invoke GetMenuItemInfoW,uhMenu,mId,0,ADDR ss_mi
mov ax,ss_buf
cmp ax,20h
jnc ss_1
mov ss_buf,0
ss_1:
invoke GetModuleHandle,0 ;程序实例句柄
mov ss_hInst,rax
invoke LoadImage,ss_hInst,BmpId,IMAGE_BITMAP,0,0,LR_SHARED or LR_DEFAULTSIZE
mov ss_mi.hbmpItem,rax
invoke ObjPool_AddItem,rax ;添加到对象池,便于程序退出时释放
invoke SetMenuItemInfoW,uhMenu,mId,NULL,ADDR ss_mi
mov rax,ss_mi.hbmpItem
ret
Menu_LoadBmp endp
;===========================================
;装入菜单---资源菜单
;入: hWin=窗口句柄。主菜单将关联到该窗口。
;返回: hMenu=主菜单句柄
; hMenuFont=菜单字体句柄
;===========================================
Load_RsrcMenu proc hWin:QWORD
;---在菜单装载或创建前先创建菜单字体---
invoke Menu_NewFont,"楷体",0,FW_BOLD
mov hMenuFont,rax
;---装入资源菜单---
invoke GetModuleHandle,0 ;程序实例句柄
invoke LoadMenu,rax,IDM_MAIN
mov hMenu,rax
invoke SetMenu,hWin,hMenu
;---装入菜单资源位图---
;---设置主菜单的无状态位图---
invoke Menu_LoadBmp,hMenu,IDM_FILE,IDB_FILE
invoke Menu_LoadBmp,hMenu,IDM_EDIT,IDB_EDIT
;---设置子菜单左边的无状态位图---
invoke Menu_LoadCheckBmp,hMenu,IDM_NEW,IDB_NEW,IDB_NEW
invoke Menu_LoadCheckBmp,hMenu,IDM_SAVE,IDB_SAVE,IDB_SAVE
invoke Menu_LoadCheckBmp,hMenu,IDM_SAVEAS,IDB_SAVEAS,IDB_SAVEAS
invoke Menu_LoadCheckBmp,hMenu,IDM_EXIT,IDB_EXIT,IDB_EXIT
invoke Menu_LoadCheckBmp,hMenu,IDM_CUT,IDB_CUT,IDB_CUT
invoke Menu_LoadCheckBmp,hMenu,IDM_COPY,IDB_COPY,IDB_COPY
invoke Menu_LoadCheckBmp,hMenu,IDM_PASTE,IDB_PASTE,IDB_PASTE
invoke Menu_LoadCheckBmp,hMenu,IDM_DEL,IDB_DEL,IDB_DEL
;---设置子菜单左边的状态位图---
invoke Menu_LoadCheckBmp,hMenu,IDM_LOCK,IDB_LOCK,IDB_UNLOCK
;---设置子菜单独立无状态位图---
invoke Menu_LoadBmp,hMenu,IDM_ABOUT,IDB_ABOUT
invoke Menu_LoadBmp,hMenu,IDM_HELP,IDB_HELP
;---如果要自己绘制菜单,则调用以下2个函数---
invoke Menu_SetBackColor,NULL,hMenu,MENU_BKCOLOR,0
invoke Menu_SetAllDraw,hMenu ;全部项目设置为自绘格式
;---重绘菜单---
invoke DrawMenuBar,hWin
mov rax,hMenu
ret
Load_RsrcMenu endp
8.3 窗口过程
以下为窗口消息过程。注意,必须在处理WM_DESTROY消息时调用ObjPool_Free函数释放对象池,对象池中已保存了14个位图句柄、1个字体句柄和1个画刷句柄,在窗口销毁时删除它们。
.code
;===========================================================
;窗口过程
;===========================================================
WndProc proc hWin:QWORD,uMsg:QWORD,wParam:QWORD,lParam:QWORD
LOCAL ss_pt:POINT
cmp rdx,WM_MEASUREITEM
jz jWM_MEASUREITEM
cmp rdx,WM_DRAWITEM
jz jWM_DRAWITEM
cmp rdx,WM_COMMAND ;uMsg
jz jWM_COMMAND
cmp rdx,WM_RBUTTONDOWN
jz jWM_RBUTTONDOWN
cmp rdx,WM_CREATE
jz jWM_CREATE
cmp rdx,WM_DESTROY
jz jWM_DESTROY
ss_def:
invoke DefWindowProc,hWin,uMsg,wParam,lParam
ret
jWM_MEASUREITEM: ;设置菜单绘制尺寸
invoke Menu_SetItemSize,hWin,hMenu,hMenuFont,lParam
ret
jWM_DRAWITEM: ;绘制菜单
invoke Menu_DrawItem,hWin,hMenu,hMenuFont,lParam
ret
jWM_CREATE:
invoke Load_RsrcMenu,hWin ;装载菜单并设置位图
mov hMenu,rax
jmp ss_ok
jWM_DESTROY: ;销毁窗口
call ObjPool_Free ;释放对象池
rcall PostQuitMessage,NULL
jmp ss_ok
jWM_RBUTTONDOWN: ;弹出快捷菜单
invoke GetCursorPos,addr ss_pt ;获得当前鼠标的坐标
add ss_pt.x,10
add ss_pt.y,10
invoke GetSubMenu,hMenu,1
invoke TrackPopupMenuEx,rax,0,ss_pt.x,ss_pt.y,hWin,0
jmp ss_ok
jWM_COMMAND:
test r9,r9
jnz ss_def
;---改变单(复)选菜单状态---
invoke Menu_ChangeCheck,hMenu,r8d ;r8d=菜单ID
;---执行菜单命令---
cmp wParam,IDM_EXIT
jnz ss_ok
rcall SendMessage,hWin,WM_CLOSE,NULL,NULL
ss_ok:
xor rax,rax
ret
WndProc endp
效果图如下:





文章介绍了如何在Windows环境下自定义绘制菜单,包括菜单项的位图、文本和分隔条,以及如何处理菜单的单选和复选状态。还提供了通用的函数来处理菜单项的位图和状态,并展示了如何在WM_DRAWITEM消息中绘制菜单项。此外,文章讨论了菜单栏背景色的设置和菜单项的初始化尺寸计算。

1238

被折叠的 条评论
为什么被折叠?



