1. 程式人生 > >Win32彙編--如何使用資源 [選單和加速鍵]

Win32彙編--如何使用資源 [選單和加速鍵]

使用資源 —— 選單和加速鍵

  主選單,頂層選單,彈出式選單,子選單,右鍵彈出式選單,系統彈出式選單。選單中的選單項有好幾種,從資源定義的角度來看,分隔用的橫線也是一個選單項,除橫線外其他選單項可以供使用者選擇,也可以設定為“禁止”或“灰化”狀態暫時停用。

  選單項上的圓點表示選中標記是互斥的,對鉤表示是不互斥的。

  加速鍵就是選單項的快捷鍵。表示當視窗是啟用的時候,不必開啟選單,直接按快捷鍵就相當於選擇了選單項。

選單和加速鍵的資源定義

資源指令碼檔案舉例:

//Menu.rc

#include <resource.h>

#define ICO_MAIN                     0x1000           //圖示

#define IDM_MAIN                     0x2000           //選單

#define IDA_MAIN                     0x2000           //加速鍵

#define IDM_OPEN                    0x4101

#define IDM_OPTION                 0x4102

#define IDM_EXIT                     0x4103

#define IDM_SETFONT               0x4201

#define IDM_SETCOLOR              0x4202

#define IDM_INACT                    0x4203

#define IDM_GRAY                     0x4204

#define IDM_BIG                        0x4205

#define IDM_SMALL                   0x4206

#define IDM_LIST                      0x4207

#define IDM_DETALL                  0x4208

#define IDM_TOOLBAR               0x4209

#define IDM_TOOLBARTEXT        0x4210

#define IDM_INPUTBAR              0x4211

#define IDM_STATUSBAR           0x4212

#define IDM_HELP                     0x4301

#define IDM_ABOUT                  0x4302

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN           ICON             “Main.ico”

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDM_MAIN           menu             discardable

BEGIN

                     popup “檔案(&F)”

                     BEGIN

                                   menuitem              “開啟檔案(&O)…”, IDM_OPEN

                                   menuitem              “關閉檔案(&C)…”, IDM_OPTION

                                   menuitem              separator

                                   menuitem              “退出(&X)”, IDM_EXIT

                     END

                     popup “檢視(&V)”

                     BEGIN

                                   menuitem              “字型(&F)…\tAlt+F”, IDM_SETFONT

                                   menuitem              “背景色(&B) …\tCtrl+Alt+B”, IDM_SETCOLOR

                                   menuitem              separator

                                   menuitem              “被禁用的選單項”, IDM_INACT, INACTIVE

                                   menuitem              “被灰化的選單項”, IDM_GRAY, GRAYED

                                   menuitem              separator

                                   menuitem              “大圖示(&G)”, IDM_BIG

                                   menuitem              “小圖示(&M)”, IDM_SMALL

                                   menuitem              “列表(&L)”, IDM_LIST

                                   menuitem              “詳細資料(&D)”, IDM_DETAIL

                                   menuitem              separator

                                   menuitem              “工具欄(&T)”

                                   BEGIN

                                                 menuitem              “標準按鈕(&S)”, IDM_TOOLBAR

                                                 menuitem              “文字標籤(&C)”, IDM_TOOLBARTEXT

                                                 menuitem              “命令欄(&I)”, IDM_INPUTBAR

                                   END

                                   menuitem              “狀態列(&U)”, IDM_STATUSBAR

                     END

                     popup “幫助(&H)” HELP

                     BEGIN

                                   menuitem              “幫助主題(&H)\F1”, IDM_HELP

                                   menuitem              separator

                                   menuitem              “關於本程式(&A)…”, IDM_ABOUT

                     END

END

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDA_MAIN           accelerators

BEGIN

                     VK_F1, IDM_HELP, VIRTKEY

                     “B”,        IDM_SETCOLOR, VIRTKEY, CONTROL, ALT

                     “F”,        IDM_SETFONT, VIRTKEY, ALT

END


//編譯上述檔案使用的makefile檔案如下:

NAME = Menu

OBJS = $(NAME).obj

RES = $(NAME).res

LINK_FLAG = /subsystem:windows

ML_FLAG = /c /coff

$(NAME).exe: $(OBJ) $(RES)

                     Link $(LINK_FLAG) $(OBJS) $(RES)

.asm .obj:

                     ml $(ML_FLAG) $<

.rc .res:

                     rc $<

clean:

                     del *.obj

                     del *.res

 為了編譯資原始檔,makefile中多了一個資源編譯的隱含規則:

.rc .res:

              rc $<

 同時在exe檔案的依賴檔案中增加了Menu.res檔案。

  在rc檔案中,各種語句使用的是C語言語法格式,因為資源編譯器Rc.exe根本上就是Visual C++附帶的,所以在定義等值語句的時候用的是#define,包含語句使用#include<檔名>,用到16進位制數值的時候並不是用匯編的語法在後面加h,而是用前面加0x的方法,如1234h寫為0x1234,註釋也要用前面加//的方法。

  在指令碼檔案的頭部,首先要把MASM32軟體包中的resource.h檔案包含進來,這個檔案中包括了資源定義中很多的預定義值,如視窗屬性與加速鍵的鍵值等。資源在程式中的引用往往用一個數值來表示,稱為資源的ID值,但在定義的時候直接使用數值不是很直觀,所以往往用#define語句將數值定義為容易記憶的字串。

選單的定義

在資源指令碼檔案中選單的定義格式是:

選單ID          MENU           [DISCARDABLE]

BEGIN

              選單項定義

              …

END

“選單ID   MENU   [DISCARDABLE]”語句用來指定選單的ID值和記憶體屬性,選單ID可以是16位的整數,範圍是1~65535,在Menu.rc檔案中,定義的選單ID是2000h,但選單ID也可以字串表示,如下面的定義:

MainMenu menu

begin

              menuitem…

end

  表示選單的ID是字串型的“MainMenu”,但這樣定義的話,在程式中引用的時候就要用字串指標代替16進位制的選單ID值,顯得相當不便,所以在實際應用中通常使用16進位制數值當做選單ID。

  數值型ID的範圍限制在1~65535之間的原因是字串在記憶體中的線性地址總是大於10000h,API函式檢測引數時發現小於10000h時就可以把它認為是數值型的,大於10000h時就當做字串指標處理。

  menu關鍵字後面的DISCARDABLE是選單的記憶體屬性,表示選單在不再使用的時候可以暫時從記憶體中釋放以節省記憶體,這是一個可選屬性。選單項的定義語句必須包含在begin和end關鍵字之內,這兩個關鍵字也可以用花括號{和}代替。

選單專案的定義方法有3類:

       MENUITEM 選單文字,命令ID[,選項列表]             (用法1)

或    MENUITEM SEPARATOR                                       (用法2)

或    POPUP 選單文字[,選項列表]                                 (用法3)

       BEGIN

                     item-definitions

                     …

       END

用法1定義的是普通選單項。

  組成部分有:

  選單文字——顯示在選單項中的字串。如果需要字串中某個字母帶下橫線,那麼可以在字母前面加&符號,帶下橫線的字母可以被系統自動當做快捷鍵。另外,如果要把加速鍵的提示資訊顯示在選單項的右邊,如“字型”選單項中的“Alt+F”字元,可以在兩者中間加\t,表示插入一個Tab字元),寫為“字型(&F)…\tAlt+F”,這樣Tab後面的字元在顯示的時候會右對齊。

  命令ID——用來分辨不同的選單項,當選單項被選中的時候,Windows會向視窗過程傳送WM_COMMAND訊息,訊息的引數就是這個命令ID。用命令ID可以分辨使用者究竟選中了哪個選單項,所以不同的選單項應該定義不同的ID值,除非想讓兩個選單項的功能相同。

  選項——用來定義選單項的各種屬性,它可以是下列數值:

       CHECKED——表示打上選定標誌(對鉤)。

       GRAYED——表示選單項是灰化的。

       INACTIVE——表示選單是禁用的。

       MENUBREAK或MENUBARBREAK——表示將這個選單項和以後的選單項列到新的列中。

  用法2定義的是選單項之間的分隔線,顯然,分隔線是不需要字串和選項的。

  用法3定義的是彈出式選單,頂層選單是由多個彈出式子選單組成的,所以在Menu.rc檔案中,主選單是由“檔案”、“檢視”、“幫助”3個順序定義的彈出式選單組成的,彈出式選單的定義也可以巢狀,如“檢視”選單中的“工具欄”又是一個彈出式選單,在巢狀的時候要注意像寫C的源程式一樣把begin和end(或者{或})正確地配對。popup選單的選項列表可以是以下的值:

       GRAYED——表示選單項是灰化的。

       INACTIVE——表示選單項是禁用的。

       HELP——表示本項和以後的選單項是右對齊的。

  popup選單項選中的時候會自動將彈出式選單彈出來,不需要向程式傳送訊息,所以在定義的引數中不需要命令ID。

  有些選項是可以同時定義,如果要指定超過一個的選項,中間要用逗號隔開,但是也有些小小的限制:GRAYED和INACTIVE不能同時使用,MENUBREAK和MENUBARBREAK也是不能同時使用的。

加速鍵的定義

  和選單的定義相比,加速鍵的定義要簡單得多,具體的語法如下:

  加速鍵ID    ACCELERATORS

  BEGIN

              鍵名,命令ID [,型別] [,選項]

              …

  END

  加速鍵ID同樣可以是一個字串或者是1~65535之間的數字,整個定義內容也是用begin和end(或花括號)包含起來,中間是多個加速鍵的定義專案,每個鍵佔據一行,各欄位的含義如下所示。

  鍵名——表示加速鍵對應的按鈕,可以有3種方式定義:

       “^字母”:表示Ctrl鍵加上字母鍵。

       “字母”:表示字母,這時型別必須指明是VIRTKEY。

          數值:表示ASCII碼為該數值的字母,這時型別必須指明為ASCII。

命令ID——按下加速鍵後,Windows向程式傳送的命令ID。如果想把加速鍵和選單項關聯起來,這裡就是要關聯期間項的命令ID。

型別——用來指定鍵的定義方式,可以是VIRTKEY和ASCII,分別用來表示“鍵名”欄位定義的是虛擬鍵還是ASCII碼。

選項——可以是Alt,Control或Shift中的單個或多個,如果指定多個,則中間用逗號隔開,表示加速鍵是按鍵加上這些控制鍵的組合鍵。這些選項只能在型別是VIRTKEY的情況下才能使用。

  在鍵名的定義中,系統按鍵如F1,F2,BackSpace和Esc等都是用虛擬鍵的方法定義的,Resource.h中已經包括所有的預定義,它們是以VK_開頭的一引起值,如VK_BACK,VK_TAB,VK_RETURN,VK_ESCAPE,VK_DELETE,VK_F1和VK_F2等,讀者可以檢視Resource.h檔案,下面是加速鍵定義的一些例子:

下面是加速鍵定義的一些例子:

“^C”,      ID                 ;Ctrl+C

“K”,        ID                 ;Shift+K

“k”,        ID,ALT          ;Alt+k

98,          ID,ASCII              ;b(字元b的ASCII碼為98)

66,          ID,ASCII              ;B(Shift b)

“g”,        ID                 ;g

VK_F1,   ID,VIRTKEY ;F1

VK_F2,   ID,VIRTKEY, CONTROL     ;Ctrl + F1

VK_F3,   ID,VIRTKEY,ALT,SHIFT      ;Alt + Shift + F2

  在一個資源指令碼檔案中,可以定義多個選單和多個加速鍵表,當然也有其他各式各樣的資源,有點陣圖、圖示與對話方塊等,這就涉及為這些資源取ID值的問題,取值的時候要掌握的原則是:

(1)對於同類別的多個資源,資源ID必須為不同的值,如定義了兩個選單,那麼它們的ID就必須用不同的數值表示,否則將無法分辨。

(2)對於不同類別的資源,資源ID在數值上可以是相同的,如可以將選單和加速鍵的ID都定義為1,同時也可以有ID為1的點陣圖或圖示等,Windows並不會把它們搞混。

使用選單和加速鍵

  在完成資原始檔所示的編寫後,來看看如何在程式中使用選單和加速鍵,這裡先列出程式的功能說明,讀者可以先嚐試一下,以便在以下的程式分析中有所印象。程式功能如下:

·程式在使用者選擇了任何一個選單項以後,會彈出一個對話方塊,將接收到的選單命令ID顯示出來。

·選擇“大圖示”、“小圖示”、“列表”和“詳細資料”選單項後,選中的選單項前面會出現一個圓點選中標記,4個選單項的選擇是互斥的。

·在視窗的客戶區單擊滑鼠右鍵會彈出和“檢視”選單一致的彈出式選單。

·在標題欄圖示上單擊滑鼠左鍵,會彈出系統選單,注意上面比預設的選單多了兩項:“幫助主題”和“關於本程式”。

  接下來,將逐步分析這些功能是如何實現的。下面是Menu.asm原始碼,程式碼是在以前的FirstWindow程式的基礎上改寫的,這是編寫Win32彙編程式的一個常用方法——拷貝一個模板程式再進行修改會節省很多的時間。

 .386

  .model flat,stdcall

  option casemap:none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 檔案定義

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include         windows.inc

include         user32.inc

includelib      user32.lib

include         kernel32.inc

includelib      kernel32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Equ 等值定義

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN        equ     1000h   ;圖示

IDM_MAIN        equ     2000h   ;選單

IDA_MAIN        equ     2000h   ;加速鍵

 

IDM_OPEN        equ     4101h

IDM_OPTION      equ     4102h

IDM_EXIT        equ     4103h

 

IDM_SETFONT     equ     4201h

IDM_SETCOLOR    equ     4202h

IDM_INACT       equ     4203h

IDM_GRAY        equ     4204h

IDM_BIG         equ     4205h

IDM_SMALL       equ     4206h

IDM_LIST        equ     4207h

IDM_DETAIL      equ     4208h

IDM_TOOLBAR     equ     4209h

IDM_TOOLBARTEXT equ     4210h

IDM_INPUTBAR    equ     4211h

IDM_STATUSBAR   equ     4212h

 

IDM_HELP        equ     4301h

IDM_ABOUT       equ     4302h

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 資料段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .data?

hInstance       dd      ?

hWinMain        dd      ?

hMenu           dd      ?

hSubMenu        dd      ?

 

                .const

szClassName     db      'Menu Example',0

szCaptionMain   db      'Menu',0

szMenuHelp      db      '幫助主題(&H)',0

szMenuAbout     db      '關於本程式(&A)...',0

szCaption       db      '選單選擇',0

szFormat        db      '您選擇了選單命令:%08x',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 程式碼段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

                .code

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 彈出視窗,顯示選單命令ID

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_DisplayMenuItem    proc    _dwCommandID

                    local @szBuffer[256]:byte

                       

                pushad

                invoke wsprintf, addr @szBuffer, addr szFormat, _dwCommandID

                invoke MessageBox, hWinMain, addr @szBuffer, offset szCaption, MB_OK

                popad

                ret

_DisplayMenuItem    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 退出

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_Quit           proc

                invoke DestroyWindow, hWinMain

                invoke PostQuitMessage, NULL

                ret

_Quit           endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 視窗過程

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcWinMain    proc uses ebx edi esi, hWnd, uMsg, wParam, lParam

                local @stPos:POINT

                local @hSysMenu

 

                mov eax,uMsg

;*************************************************************************

                .if     eax == WM_CREATE

                        invoke     GetSubMenu, hMenu, 1

                        mov     hSubMenu, eax

;***************************************************************************

; 在系統選單中新增選單項

;***************************************************************************

                        invoke GetSystemMenu, hWnd, FALSE

                        mov     @hSysMenu, eax

                        invoke AppendMenu, @hSysMenu, MF_SEPARATOR, 0, NULL

                        invoke AppendMenu, @hSysMenu, 0, IDM_HELP, offset szMenuHelp

                        invoke AppendMenu, @hSysMenu, 0, IDM_ABOUT, offset szMenuAbout

;***************************************************************************

; 處理選單及加速鍵訊息

;***************************************************************************

                .elseif eax == WM_COMMAND

                        invoke _DisplayMenuItem, wParam

                        mov     eax, wParam

                        movzx   eax, ax

                        .if     eax == IDM_EXIT

                                call    _Quit

                        .elseif eax >= IDM_TOOLBAR && eax <= IDM_STATUSBAR

                                mov ebx, eax

                                invoke GetMenuState, hMenu, ebx, MF_BYCOMMAND

                                .if     eax == MF_CHECKED

                                        mov eax, MF_UNCHECKED

                                .else

                                        mov eax, MF_CHECKED

                                .endif

                                invoke CheckMenuItem, hMenu, ebx, eax

                        .elseif eax >= IDM_BIG && eax <= IDM_DETAIL

                                invoke CheckMenuRadioItem, hMenu, IDM_BIG, IDM_DETAIL, eax, MF_BYCOMMAND

                        .endif

;***************************************************************************

; 處理系統選單訊息

;***************************************************************************

                .elseif eax == WM_SYSCOMMAND

                        mov     eax, wParam

                        movzx   eax, ax

                        .if     eax == IDM_HELP || eax == IDM_ABOUT

                                invoke _DisplayMenuItem, wParam

                        .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

;***************************************************************************

                xor     eax,eax

                ret

_ProcWinMain    endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_WinMain        proc

                local   @stWndClass:WNDCLASSEX

                local   @stMsg:MSG

                local   @hAccelerator

 

                invoke     GetModuleHandle,NULL

                mov     hInstance,eax

                invoke LoadMenu, hInstance, IDM_MAIN   ;獲取選單控制代碼

                mov     hMenu, eax                      ;把選單控制代碼儲存於hMenu變數

                invoke LoadAccelerators, hInstance,IDA_MAIN

                mov     @hAccelerator, eax

                

                invoke     RtlZeroMemory,addr @stWndClass,sizeof @stWndClass

;**************************************************************************

; 註冊視窗類

;**************************************************************************

                invoke LoadIcon, hInstance, ICO_MAIN

                mov     @stWndClass.hIcon, eax

                mov     @stWndClass.hIconSm, eax

                invoke LoadCursor, 0, IDC_ARROW

                mov     @stWndClass.hCursor, eax

                push    hInstance

                pop     @stWndClass.hInstance

                mov     @stWndClass.cbSize, sizeof WNDCLASSEX

                mov     @stWndClass.style, CS_HREDRAW or CS_VREDRAW

                mov     @stWndClass.lpfnWndProc, offset _ProcWinMain

                mov     @stWndClass.hbrBackground,COLOR_WINDOW + 1

                mov     @stWndClass.lpszClassName, offset szClassName

                invoke     RegisterClassEx, addr @stWndClass

;***************************************************************************

; 建立並顯示視窗

;***************************************************************************

                invoke     CreateWindowEx, WS_EX_CLIENTEDGE, \

                        offset szClassName, offset szCaptionMain, \

                        WS_OVERLAPPEDWINDOW, \

                        100, 100, 400, 300, \

                        NULL, hMenu, hInstance, NULL

                mov     hWinMain,eax

                invoke     ShowWindow,hWinMain,SW_SHOWNORMAL

                invoke     UpdateWindow,hWinMain

;**************************************************************************

; 訊息迴圈

;**************************************************************************

                .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

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

start:

                call    _WinMain

                invoke ExitProcess, NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  end start

 

1、載入選單

在視窗中載入選單的方法有兩個:一是在註冊視窗類的時候指定類的預設選單;二是在建立視窗的時候在引數中指定選單控制代碼。Menu.asm程式中用的是第2種方法:

       invoke CreateWindowEx, WS_EX_CLIENTEDGE, \

                     offset szClassName, offset szCaptionMain, \

                     WS_OVERLAPPEDWINDOW,\

                     100, 100, 400, 300, \

                     NULL, hMenu, hInstance, NULL

在引數中指出了hMenu。不管用哪種方法,首先都必須使用LoadMenu函式來獲取選單控制代碼hMenu,如下面的語句:

       invoke     LoadMenu, hInstance, IDM_MAIN

       mov hMenu, eax

這個函式的第1個引數是用GetModuleHandle獲取的例項控制代碼,第2個引數指定需要裝入的選單資源ID,函式返回選單控制代碼。在得到選單控制代碼以後,我們先把它放入hMenu變數儲存起來以便後用。

當資原始檔中用字串為名稱定義選單而不是用數值的時候,例如:

       MainMenu      menu             //定義選單名為字串“MainMenu”

       begin

                     …

       end

那麼在程式中就必須用字串指標代替選單ID做引數:

       szMenu   “MainMenu”,0              ;在資料段中定義選單名稱字串

                     …

                     invoke     LoadMenu, hInstance, addr szMenu            ;在程式中裝載

                     mov        hMenu, eax

注:用字串為名稱定義資源,在資源裝載函式LoadXXXX中使用者字串指標做引數裝入,這實際上是一個通用的方法,不僅適用於選單資源,對於其他類別的資源也是適用的。

2、載入加速鍵

和選單一樣,加速鍵在使用前也要裝入,引數同樣是在資源指令碼檔案中定義的加速鍵ID,程式中對應的語句是:

       invoke     LoadAccelerators, hInstance, IDA_MAIN

       mov        @hAccelerator, eax

其實我們自己在程式中也可以很方便地實現加速鍵功能,方法是:在WM_KEYDOWN訊息中判斷鍵盤訊息並按照自定義的邏輯進行處理,使用加速鍵實際上是讓Windows替我們完成這個功能,Windows實現的方法正是在訊息迴圈中檢查WM_KEYDOWN和WM_SYSKEYDOWN訊息。下面是使用加速鍵時訊息迴圈的程式碼,請注意粗體字部分:

.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函式是實現加速鍵功能的核心,它的引數為目標視窗、加速鍵控制代碼和GetMessage取得的訊息結構。該函式檢查訊息結構中的訊息,如果遇到WM_KEYDOWN和WM_SYSKEYDOWN訊息則檢測加速鍵資源,看按鍵是否符合某個加速鍵,符合的話則向目標視窗傳送WM_COMMAND或WM_SYSCOMMAND訊息,並返回TRUE,不符合的話不進行任何處理並返回FALSE。

  由於加速鍵的鍵碼並不是使用者真正想輸入視窗的,比如使用者在寫字板中輸入文字,按Ctrl+C鍵是為了“拷貝”,而不是想把Ctrl+C鍵對應的字元輸入文件,所以這個Ctrl+C的鍵碼在完成加速鍵的使命後就應該丟棄,也就是說符合加速鍵的鍵盤訊息不應該再發送給視窗,TranslateMessage和DispatchMessage函式前的邏輯判斷就是這樣的意圖:只有TranslateAccelerator沒有轉換的訊息(返回值eax為0)才繼續處理。

  另外,TranslateAccelerator的引數中有個“目標視窗”,例子中是程式的主視窗hWinMain,為什麼要設定這樣一個引數而不像DispatchMessage函式一樣使用MSG結構中的hwnd呢?這是因為鍵盤訊息可以產生於不同視窗中——既可能是主視窗,也可能是其他子視窗,如果用@stMsg.hwnd做目標視窗,就必須在所有子視窗的視窗過程中都設定處理加速鍵訊息的程式碼,這顯然是一種浪費,所以一般把所有的加速鍵訊息都發送到主視窗,然後集中在主視窗的視窗過程中處理WM_COMMAND訊息,這樣有利精簡程式碼。

3、選單和加速鍵訊息

當用戶選擇了一個選單項的時候,Windows向選單所屬的視窗傳送WM_COMMAND訊息;而使用者按下了一個加速鍵的時候,Windows向TranslateAccelerator函式指定的目標視窗傳送WM_COMMAND訊息。一般這兩種情況對應的視窗都是主視窗,所以可以在主視窗中的視窗過程中集中處理WM_COMMAND訊息,而不必考慮它究竟是選單引發的還是加速鍵引發的。

WM_COMMAND訊息的兩個引數是這樣定義的:

wParam的高位 = wNotifyCode         ;通知碼

wParam的低位 = wID                      ;命令ID

lParam =hwndCtl                               ;傳送WM_COMMAND的子視窗控制代碼

除了選單和加速鍵,WM_COMMAND訊息也可以由其他子視窗引發,如主視窗中的按鈕或工具欄等,lParam引數指定了引發訊息的子視窗控制代碼,對於選單和加速鍵引發的WM_COMMAND訊息,lParam的值為零。wParam引數的低16位是命令ID,也就是資源指令碼檔案中選單項的命令ID或加速鍵的命令ID,高16位是通知碼,選單訊息的通知碼是0,加速鍵訊息的通知碼為1。

在需要處理選單和加速鍵訊息的視窗過程中,一般需要增加一個WM_COMMAND分支來處理對應的訊息,這個分支的一般結構為:

       .elseif             eax == WM_COMMAND     ;eax中為wMsg

                            mov        eax, wParam

                            movzx     eax, ax

                            .if           eax == 命令ID1

                                          …

                            .elseif      eax == 命令ID2

                                          …

                            .endif

其中movzx eax, ax指令將16位的ax擴充套件到32位的eax,相當於將eax的高16位清零,作用就是當訊息由加速鍵引起時,將高16位中的1忽略,這樣下面的分支就可以同時處理選單和加速鍵訊息,當然讀者也可以去掉這一句,這樣下面的比較語句中就要使用ax而不是eax。

在例子程式中,mov eax, wParam前面還有一句invoke _DisplayMenuItem, wParam, 作用是在處理WM_COMMAND訊息前將wParam的值通過一個對話方塊顯示出來,讀者可以和資源指令碼檔案中定義的命令ID值對比一下,在正常使用的程式中可以去掉這一句。

讀者可以發現,資原始檔中定義的“字型”選單項的ID為0x4201,當選中“字型”選單項的時候,對話方塊中顯示的wParam數值正是00004201,而按下加速鍵Alt+F的時候,顯示出來的值卻是00014201了,它們的區別就是高16位中的通知碼不同。

4、選單項的修改

在程式的執行中也可以動態修改選單項,包括新增、刪除和修改操作,這些操作是通過幾個API函式來完成的:

       invoke     AppendMenu, hMenu, uFlags, uIDNewItem, lpNewItem           ;新增選單項

       invoke     InsertMenu, hMenu, uPosition, uFlags, uIDNewItem, lpNewItem     ;插入選單項

       invoke     ModifyMenu, hMenu, uPosition, uFlags, uIDNewItem, lpNewItem   ;修改選單項

       invoke     DeleteMenu, hMenu, uPosition, uFlags        ;刪除選單項

       invoke     RemoveMenu, hMenu, uPosition, uFlags      ;刪除選單項

  其中AppendMenu用來在一個選單的最後新增選單項,InsertMenu則在中間插入選單項,ModifyMenu可以修改一個選單項的文字,DeleteMenu和RemoveMenu則可以刪除一個選單項。

  這些函式中的引數都雷同的,hMenu引數指要操作的選單控制代碼;uPosition用來定位要操作的選單項,定位的方法有兩種:用命令ID定義或用位置索引,用哪一種方法取決於後面的uFlags引數,當uFlags為MF_BYCOMMAND時,uPosition為選單項的命令ID,而當uFlags為MF_BYPOSITION的時候,uPosition表示選單項的位置索引,索引是從0開始的,也就是說第一個選單項的索引值為0。

  AppendMenu函式總是在選單的最後新增新的選單項,所以不需要uPosition引數。

  對於AppendMenu和InsertMenu,會有一個新的選單項產生,uIDNewItem就表示這個新選單項的命令ID,lpNewItem指向新選單項的文字字串,ModifyMenu函式可以修改一個選單項的命令ID或文字字串,所以也有uIDNewItem和lpNewItem引數。而用來刪除選單項的DeleteMenu和RemoveMenu顯然用不著uIDNewItem和lpNewItem引數。

  uFlags引數除了指定MF_BYCOMMAND還是MF_BYPOSITION外,還可以組合指定選單項的其他屬性,如MF_CHECKED,MF_DISABLED, MF_ENABLED, MF_GRAYED, MF_MENUBARBREAK, MF_MENUBREAK, MF_SEPARATOR和MF_UNCHECKED等,從其字面上就可以看出這些屬性的含義。

  DeleteMenu和RemoveMenu的不同之外在於對popup選單項的處理。當它們用於popup屬性的選單項時,DeleteMenu不僅刪除選單項,而且將這個popup選單項的所有子專案全部刪除,這樣,這個popup選單就不能在別的地方繼續使用;而RemoveMenu僅從選單中移去這個popup選單項,整個popup選單在記憶體中還是存在的。以Menu.asm程式為例,掃滑鼠右鍵彈出的選單實際上是主選單中的“檢視”選單項,假如用DeleteMenu刪除主選單中的“檢視”專案,那麼按右鍵也就彈不出選單了,而用RemoveMenu刪除主選單中的“檢視”專案,按滑鼠右鍵依然可以彈出選單。對於非popup屬性的選單項,DeleteMenu和RemoveMenu的效果是同樣的。

5、使用系統選單

  系統選單指按下了標題欄圖示後彈出的選單,和視窗選單不同,選中系統選單的選單項後,Windows向視窗傳送的是WM_SYSCOMMAND訊息而非WM_COMMAND訊息。預設的系統選單中已經有“還原”、“移動”、“大小”、“最大化”、“最小化”和“關閉”等選單項,這些選單項的命令ID已經預定義為SC_RESTORE,SC_MOVE,SC_SIZE,SC_MAXIMIZE,SC_MINIMIZE和SC_CLOSE等,如果讀者要自己處理它們,可以在WM_SYSCOMMAND訊息中建立一個比較分支對它們進行處理,一般在程式中並不自己處理WM_SYSCOMMAND訊息,而是交給DefWindowProc處理。

  如何在系統選單中新增自己的選單項呢?方法就是使用上面介紹的AppendMenu(當然也可以用InsertMenu),在新增前必須用GetSystemMenu函式首先獲取系統選單的控制代碼。例子程式在視窗初始化的時候在系統選單尾添加了一個分隔線和兩個選單項:“幫助主題”和“關於本程式”:

       .if    eax == WM_CREATE

              …

              invoke     GetSystemMenu, hWnd, FALSE

              mov        @hSysMenu, eax

              invoke     AppendMenu, @hSysMenu, MF_SEPARATOR, 0, NULL

              invoke     AppendMenu, @hSysMenu, 0, IDM_HELP, offset szMenuHelp

              invoke     AppendMenu, @hSysMenu, 0, IDM_ABOUT, offset szMenuAbout

在視窗過程中處理系統選單訊息的分支結構為:

       .elseif      eax == WM_SYSCOMMAND

                     mov        eax, wParam

                     .if           ax == 自定義ID1

                                   …

                     .else

                                   invoke     DefWindowProc, hWnd, uMsg, wParam, lParam

                                   ret

                     .endif

  和處理WM_COMMAND訊息不同的是,在WM_SYSCOMMAND訊息中處理了自定義的選單命令ID後,必須把其他命令ID交給DefWindowProc處理,並直接把返回值返回給Windows,不然的話會發現視窗不能移動,不能關閉,不能最小化……因為它相當於螢幕了所有SC_RESTORE, SC_MOVE, SC_SIZE, SC_MAXIMIZE, SC_MINIMIZE和SC_CLOSE等訊息的處理。

6、右鍵彈出選單

  例子程式的一個功能是當用戶在視窗客戶區按下滑鼠右鍵的時候彈出一個選單,這個功能是用TrackPopupMenu函式實現的。TrackPopupMenu函式的用法:

       invoke     TrackPopupMenu, hMenu, uFlags, x, y, nReserved, hWnd, lpRect

  這個函式本身很簡單,執行後在引數指定的x,y位置彈出一個屬於hWnd視窗(也就是說WM_COMMAND訊息發到這個視窗)的選單,選單控制代碼是hMenu。函式中的座標是以整個螢幕左上角為基準的,所以彈出選單的位置不一定在視窗的客戶區內,它可以是螢幕的任何一個地方。

  uFlags引數指定一些和位置相關的選項,它可以是PM_CENTERALIGN,TPM_LEFTALIGN或TPM_RIGHTALIGN三者之一,表示(x, y)座標是代表彈出選單位置的中間、左上角還是右上角,一般的習慣是使用TPM_LEFTALIGN,這樣選單會在滑鼠點選處的右邊彈出。uFlags中同時還可以指定用滑鼠左鍵還是右鍵選定選單項,定義值可以是TPM_LEFTBUTTON或TPM_RIGHTBUTTON,如果選擇TPM_RIGHTBUTTON的話,對在選單項上面按滑鼠左鍵是沒有反應的。

  lpRect指向一個RECT結構,用來指定一個區域,當選單彈出後,在這個區域外單擊滑鼠,選單才會消失,如果這個引數指定為NULL的話,在選單之外單擊滑鼠,選單就會消失。

在使用TrackPopupMenu之前,有幾個準備工作是要做的:為了在客戶區中按下滑鼠右鍵彈出選單,我們當然要處理滑鼠右鍵訊息,也就是說在WM_RBUTTIONDOWN訊息中呼叫TrackPopupMenu函式,一般的習慣是在滑鼠按下的地方彈出選單,所以還要首先獲取滑鼠游標的位置,然後在此位置彈出選單。

要獲取滑鼠位置,可以用GetCursorPos函式:

       invoke GetCusorPos, lpPoint

引數lpPoint指向一個POINT資料結構,這個結構只有兩個欄位:

POINT STRUCT

x     DWORD       ?

y     DWORD        ?

POINT ENDS

該結構用來表示一個點的(x, y)座標,GetCursorPos將當前的滑