大多數(shù)Windows程序都包含一個自訂的圖標,,Windows將該圖標顯示在應用程序窗口標題列的左上角。當程序被列在「開始」菜單中,,被顯示在屏幕底部的工作列中,,被列在Windows Explorer中,,或者作為快捷方式顯示在桌面上時,,Windows也顯示該程序的圖標。有些程序-大部分是像小畫家一類的圖形繪制工具-也使用自訂鼠標光標來表示程序的不同操作,。還有許多Windows程序使用菜單和對話框。菜單,、對話框加上滾動條,,這是標準Windows使用者接口的賣點,。 圖標、光標,、菜單和對話框都是相互關(guān)聯(lián)的,,它們是Windows的全部資源型態(tài)。資源即數(shù)據(jù),,它們被儲存在程序的.EXE文件中,但是它們并非駐留在程序的數(shù)據(jù)區(qū)域中,。也就是說,,資源不能從程序原始碼中定義的變量直接存取,Windows提供函數(shù)直接或間接地把它們加載內(nèi)存以備使用,。我們已經(jīng)遇到了兩個這樣的函數(shù),,即LoadIcon和LoadCursor,,它們出現(xiàn)在范例程序,,定義窗口類別結(jié)構(gòu)的內(nèi)容設定敘述中,。它們從Windows中加載二進制圖標和光標映象,,并傳回該圖標或光標的句柄。在本章中,,我們先建立自己的圖標,,它會從程序自己的.EXE文件中載入,。 在本書中,我們將討論這些資源:
前六個資源在本章討論,,對話框在第十一章討論,而位圖在第十四章討論,。 使用資源的好處之一,在于程序的許多組件能夠連結(jié)編譯進程序的.EXE文件中,。如果沒有資源這一個概念,,如圖標圖像之類的二進制文件可能會存放在單獨的文件中,,.EXE會把它讀入內(nèi)存中使用。或者圖標不得不在程序中以字節(jié)數(shù)組的形式定義(這樣就無法看到實際的圖標圖像了),。作為資源,,圖標儲存在開發(fā)者計算機上可單獨編輯的文件中,,但在編譯程序中被連結(jié)編譯進.EXE文件中,。 將圖標添加到程序 將資源添加到程序中需要Visual C++ Developer Studio的一些附加功能,。對于圖示來說,可以使用「Image Editor」(也稱為「Graphics Editor」)來繪制圖標的圖像,。該圖像被儲存在擴展名為.ICO的圖示文件中。Developer Studio還產(chǎn)生一個資源描述檔(擴展名為.RC的文件,,有時也稱作資源定義文件),,它列出了程序的所有資源和一個讓程序引用資源的表頭文件(RESOURCE.H),。 因此,您可以看到這些新文件是如何組織在一起的,,讓我們以建立名為ICONDEMO的新項目開始,。像往常一樣,在Developer Studio中從File菜單中選擇New,,然后依次選擇 項目頁面標簽和Win32 Application,。在Project Name欄中鍵入ICONDEMO并單擊OK,。這時,Developer Studio建立了用于支持工作區(qū)和項目的五個文件,。這些文件包括文本文件ICONDEMO.DSW,、ICONDEMO.DSP和ICONDEMO.MAK(假設當您從 Tools菜單選擇Open后,在顯示的 Open對話框中,,從Build頁面標簽中選中 Export makefile when saving project file)。現(xiàn)在,讓我們像通常那樣所做的建立C原始碼文件,。從 File菜單上選擇New,選擇Files頁面標簽,,并單擊 C++Source File。在File Name欄中鍵入ICONDEMO.C并單擊OK,。此時,Developer Studio就建立了一個空的ICONDEMO.C文件,。鍵入程序10-1中的程序,,或選擇 Insert菜單,然后選擇File As Text選項,從本書附上的光盤中復制原始碼。 程序10-1 ICONDEMO
ICONDEMO.C /*-------------------------------------------------------------------------- ICONDEMO.C -- Icon Demonstration Program (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { TCHAR szAppName[] = TEXT ("IconDemo") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Icon Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HICON hIcon ; static int cxIcon, cyIcon, cxClient, cyClient ; HDC hdc ; HINSTANCE hInstance ; PAINTSTRUCT ps ; int x, y ; switch (message) { case WM_CREATE : hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ; cxIcon = GetSystemMetrics (SM_CXICON) ; cyIcon = GetSystemMetrics (SM_CYICON) ; return 0 ; case WM_SIZE : cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; for (y = 0 ; y < cyClient ; y += cyIcon) for (x = 0 ; x < cxClient ; x += cxIcon) DrawIcon (hdc, x, y, hIcon) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } 如果您試著編譯該程序,因為在程序開頭引用的RESOURCE.H文件并不存在,,所以會產(chǎn)生錯誤,。然而,,您不必直接建立RESOURCE.H文件,,而是由Developer Studio為您建立一個。 您可以通過將資源描述檔添加到項目中來做到這一點。從「File」菜單中選擇「New」,,選擇「Files」頁面標簽,單擊「Resource Script」,,在「File Name」欄中鍵入「ICONDEMO」,單擊OK。此時,Developer Studio會建立兩個文本文件:ICONDEMO.RC(資源描述檔)和RESOURCE.H(允許C原始碼文件和資源描述檔引用相同的已定義標識符),。不必直接編輯這兩個檔案,只要讓Developer Studio來維護它們就可以,。如果您想查看資源描述檔和RESOURCE.H而不希望對Developer Studio產(chǎn)生干擾,,可以用記事本打開它們,。除非您對所做的動作很有把握,,否則不要輕易地更改它們。請記住,,只有在您下達明確的操作命令或重新編譯項目時,,Developer Studio才會儲存這些文件的新版本。(來源:華 軟 源 碼) 資源描述檔是文本文件,。它包括這些資源的可用文字形式表達的描述,,例如菜單和對話框。資源描述文件也包括對非文字資源的二進制檔案的引用,,例如圖標和自訂的鼠標光標,。 現(xiàn)在,已經(jīng)存在RESOURCE.H文件,,您可以試著重新編譯一下ICONDEMO?,F(xiàn)在會出現(xiàn)一條錯誤消息,指出IDI_ICON還沒被定義,。這個標識符第一次出現(xiàn)在下面的敘述中: wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ; 在本書前面的程序中,,這個敘述是由下面的敘述代替的: wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; 之所以改變敘述,是因為以前我們?yōu)閼贸绦蚴褂玫氖菢藴实膱D示,,而這里我們的目的是使用自訂圖示,。 那么讓我們建立一個圖示吧!在Developer Studio的「File View」窗口中,,您會看到兩個文件-ICONDEMO.C和ICONDEMO.RC,。您開啟CONDEMO.C后,就可以編輯原始碼,。開啟ICONDEMO.RC后,,就可以把資源添加到文件中或編輯已存在的資源。要添加圖示的話,,請從「 Insert」菜單上選擇「Resource」選擇您想添加的資源,,也就是圖示,然后再按下「 New」按鈕,。 現(xiàn)在呈現(xiàn)的是一個空白的32×32圖素的圖示,,您可以在其中填入顏色。您會看到帶有一組繪圖工具和可用顏色的浮動工具列,。注意顏色工具列中包括兩個與顏色無關(guān)的選項,,這兩種顏色選項有時被稱為「屏幕顏色」跟「反屏幕顏色」。當一個圖素在著色時選擇了「屏幕顏色」時,它實際上是透明的,。不管圖標在什么表面上顯示,,圖示未著色的部分會顯示出底色。這樣我們就可以建立非矩形的圖示,。 雙擊圍繞圖標的區(qū)域,會出現(xiàn)「Icon Properties」對話框,,該對話框使您能夠更改圖標的ID和文件名稱,。Developer Studio可能已經(jīng)將ID設定為IDI_ICON1,將它改為IDI_ICON,,這樣ICONDEMO就可以引用圖標(前綴IDI代表「圖標的ID」),。同樣地,將文件名改為ICONDEMO.ICO,。 現(xiàn)在選擇一種有特色的顏色(如紅色)并在圖示上畫一個大的B(代表BIG),,請注意不必像圖10-1那么整齊。
此時程序應該能夠編譯并執(zhí)行得很好了,。Developer Studio將在ICONDEMO.RC資源描述檔中劃一條橫線,,表示下面是帶有標識符(IDI_ICON)的圖示文件(ICONDEMO.ICO)。RESOURCE.H表頭文件中會包含IDI_ICON標識符的定義,。 Developer Studio通過資源編譯器RC.EXE編譯資源,。文字資源描述文件被轉(zhuǎn)化為二進制形式,也就是具有擴展名.RES的文件,。然后,,該已編譯的資源文件隨同.OBJ和.LIB文件一起在LINK步驟中被指定連結(jié)。這就是資源被添加到最后產(chǎn)生出來的.EXE文件中的方式,。 當您執(zhí)行ICONDEMO時,,程序圖標顯示在標題列的左上角和工作列中。如果您將程序添加到「開始」菜單中,,或在桌面上放置快捷方式,,您也會在那兒看到該圖示。 ICONDEMO也在顯示區(qū)域水平和垂直地重復顯示該圖示,。程序使用敘述 hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ; 取得圖示的句柄,。使用敘述 cxIcon = GetSystemMetrics (SM_CXICON) ; cyIcon = GetSystemMetrics (SM_CYICON) ; 取得圖示的大小。然后,,程序通過多次呼叫 DrawIcon (hdc, x, y, hIcon) ; 顯示圖標,,其中x和y是被顯示圖示其左上角的坐標。 在目前使用的大多數(shù)視訊顯示卡上,,帶有SM_CXICON和SM_CYICON索引的GetSystemMetrics會回報圖示的大小為32×32圖素,。這是我們在Developer Studio中建立的圖示大小,它也是圖示出現(xiàn)在桌面上和顯示在ICONDEMO程序顯示區(qū)域的大小,。然而,這個大小并非顯示在程序的標題列或工作列中的圖示大小,。小圖示的大小可以由帶有SM_CXSMSIZE和SM_CYSMSIZE索引的GetSystemMetrics獲得(第一個SM表示「system metrics(系統(tǒng)度量)」,被包含的SM表示「small(?。梗?。對于目前使用的大多數(shù)顯示卡來說,小圖示的大小為16×16圖素,。 這會產(chǎn)生問題,。當Windows將32×32的圖示縮小為16×16的圖示時,必需減少圖素的行和列,。這樣,,對于某些比較復雜的圖示,就會失真,。因此,,我們應該為那些圖像縮小就會變形的圖示建立特殊的16×16圖素的圖示。在Developer Studio中圖標圖像的上面是標識為「Device」的下拉式清單方塊,,在它的右邊有一個按鈕,,按下該按鈕會彈出「New Icon Image」對話框,此時選擇「Small(16×16)」?,F(xiàn)在您可以畫另一個圖示,。如圖10-2所示,畫一個「S」(表示「小」),。
在該程序中您不必做任何事情。第二個圖標圖像被儲存在相同的ICONDEMO.ICO文件中,,并以相同的IDI_ICON標識符引用,。在適當?shù)臅r候,Windows會自動使用該較小的圖示,,例如在標題列或工作列中,。當在桌面上顯示快捷方式,以及程序呼叫DrawIcon裝飾顯示區(qū)域時,,Windows會使用大圖示,。 在掌握這些知識之后,讓我們看一看使用圖示的詳細情況,。 取得圖示句柄 如果您仔細閱讀ICONDEMO.RC和RESOURCE.H文件,,會看到由Developer Studio產(chǎn)生用于維護文件的一些標記。然而,,當編譯資源描述檔時,,只有少數(shù)幾行是重要的。這些從ICONDEMO.RC和RESOURCE.H文件中摘錄下來的關(guān)鍵部分被列在程序10-2中。 ICONDEMO.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Icon IDI_ICON ICON DISCARDABLE "icondemo.ico" RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by IconDemo.rc #define IDI_ICON 101
程序10-2所顯示的ICONDEMO.RC和RESOURCE.H文件與您在普通的文字編輯器中手動建立的很相似,,80年代的Windows程序?qū)懽髡呔褪沁@樣做的,。唯一不同的是AFXRES.H,它是個表頭文件,,包含了在建立由機器產(chǎn)生的MFC項目時由Developer Studio使用的常用標識符,。在本書中,我們不會用到AFXRES.H,。 ICONDEMO.RC中的這行 IDI_ICON ICON DISCARDABLE "icondemo.ico" 是資源描述檔的ICON敘述,。該圖示有一個數(shù)值標識符IDI_ICON,等于101,。由Developer Studio添加的DISCARDABLE關(guān)鍵詞指出,必要時Windows可以從內(nèi)存中丟棄圖標,,以獲得額外的空間,。之后不需要程序任何特定的操作,Windows就能夠重新加載圖示,。DISCARDABLE屬性是內(nèi)定的,,不需要指定。只有在名稱和目錄路徑包含空格時,,Developer Studio才將文件名加上引號,。 當資源編譯程序?qū)⒕幾g的資源儲存在ICONDEMO.RES中,并且由連結(jié)程序?qū)①Y源添加到ICONDEMO.EXE中以后,,該資源就可以經(jīng)由一個資源型態(tài)(RT_ICON)和一個標識符(IDI_ICON或101)來標識,。程序可以通過呼叫LoadIcon函數(shù)取得此圖示的句柄: hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ; 請注意ICONDEMO在兩個地方呼叫這個函數(shù),一次在定義窗口類別時,,另一次在窗口消息處理程序中取得圖標的句柄用于繪制,。LoadIcon傳回HICON型態(tài)的值,它是圖示的句柄,。 LoadIcon的第一個參數(shù),,是指出資源來自哪個文件的執(zhí)行實體句柄。使用hInstance表示它來自程序自己的.EXE文件,。LoadIcon的第二個參數(shù)實際上被定義為指向字符串的指針,。待會將會看到,可以使用字符串而不是用數(shù)值標識符標識資源,。宏MAKEINTRESOURCE(把整數(shù)轉(zhuǎn)換成資源字符串)生成指向非數(shù)字的指針,,如下所示: #define MAKEINTRESOURCE(i) (LPTSTR) ((DWORD) ((WORD) (i))) LoadIcon知道,如果第二個參數(shù)的高字組為0,,那么低字組就為圖示的數(shù)值標識符,。圖標的標識符必須為16位值。 本書前面的范例程序使用了預先定義的圖示: LoadIcon (NULL, IDI_APPLICATION) ; hInstance參數(shù)被設定為NULL,因此Windows知道這是預先定義的圖示,。IDI_APPLICATION也在WINUSER.H中用MAKEINTRESOURCE定義: #define IDI_APPLICATION MAKEINTRESOURCE(32512) LoadIcon的第二個參數(shù)帶來了一個有趣的問題:圖標的標識符能可以為字符串嗎,?答案是可以。方法如下:在 Developer Studio中,,在ICONDEMO項目的文件列表上,,選擇 IDONDEMO.RC。您會看到頂端為「IconDemo Resource」的樹狀結(jié)構(gòu),,然后是資源型態(tài)「Icon」,,再下來是「IDI_ICON」。如果用鼠標右鍵單擊圖標標識符,,并從菜單上選擇「 Properties」,,您就能改變ID。實際上,,您可以把名稱放在引號內(nèi)將其更改為字符串,。我用這種方法指定資源名稱,并在本書的其它地方也使用該方法,。 我喜歡為圖示(以及一些其它資源)使用文字名稱,,因為名稱可以是程序的名稱。例如,,假定文件被命名為MYPROG,。如果您使用「Icon Properties」對話框?qū)D標的ID指定為「MyProg」(包括引號),資源描述檔將包含下列敘述: MYPROG ICON DISCARDABLE myprog.ico 然而,,在RESOURCE.H中并沒有#define敘述,,來指出MYPROG是數(shù)值標識符。資源描述文件將假定MYPROG是字符串標識符,。 在C程序中,,使用LoadIcon函數(shù)來取得圖示句柄。您可能已經(jīng)有了表示程序名的字符串: static TCHAR szAppName [] = TEXT ("MyProg") ; 這意味著程序可以使用敘述: hIcon = LoadIcon (hInstance, szAppName) ; 來加載圖標,,這比宏MAKEINTRESOURCE更清晰一些,。 但是如果您確實想用數(shù)字來命名,那么您可以用數(shù)字代替標識符或字符串,。在「Icon Properties」對話框中,,在ID欄中輸入數(shù)字。資源描述檔將有一個類似下面的ICON敘述: 125 ICON DISCARDABLE myprog.ico 可以使用兩種方法之一引用圖示,。明顯易讀的方式是: hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (125)) ; 另一個不易閱讀的方式是: hIcon = LoadIcon (hInstance, TEXT ("#125")) ; Windows識別初始字符#作為ASCII形式中字符數(shù)值的開頭,。 在程序中使用圖標 雖然Windows以幾種方式用圖標來代表程序,但是許多Windows程序僅在用WNDCLASS結(jié)構(gòu)和RegisterClass定義窗口類別時指定一個圖示,。如我們所看到的,,這樣作用得很好,,尤其當圖示文件包含標準和較小的圖像大小時,更是如此,。Windows在顯示圖標圖像時,,它會在圖示文件中選擇最合適的圖像大小。 RegisterClass有一個改進版本叫做RegisterClassEx,,它使用名為WNDCLASSEX的結(jié)構(gòu),。WNDCLASSEX有兩個附加的字段:cbSize和hIconSm。cbSize字段指出了WNDCLASSEX結(jié)構(gòu)的大小,,假設hIconSm被設定為小圖標的圖標句柄,。這樣,在WNDCLASSEX結(jié)構(gòu)中,,您可以設定與兩個圖示文件相關(guān)的兩個圖示句柄-一個用于標準圖示,,一個用于小圖示。 有這種必要嗎,?沒有,。正如我們看到的,Windows已經(jīng)從單個圖示文件中提取了大小合適的圖標圖像,。RegisterClassEx似乎沒有RegisterClass聰明。如果hIconSm字段使用了包含多個圖像的圖標文件,,則只有第一個圖像能被利用,。它可能是標準大小的圖示,使用時才被縮小,。RegisterClassEx似乎是為了使用多個圖標圖像而設計的,,每個圖像只包含一種圖標大小。因為現(xiàn)在可以將多個圖示大小包括在同一個圖示文件中,,所以我建議使用WNDCLASS和RegisterClass,。 如果您想在程序執(zhí)行的時候,動態(tài)地更改程序的圖標,,可以使用SetClassLong來達到目的,。例如,如果您有與標識符IDI_ALTICON相關(guān)的第二個圖示文件,,則您可以使用以下的敘述將其切換到那個圖示: SetClassLong (hwnd, GCL_HICON, LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ALTICON))) ; 如果不想儲存程序圖標的句柄,,但要使用DrawIcon函數(shù)在別處顯示它,可以使用GetClassLong獲得句柄,。例如: DrawIcon (hdc, x, y, GetClassLong (hwnd, GCL_HICON)) ; 在Windows文件的某些部分,,LoadIcon被稱為「過時的」,并推薦使用LoadImage(LoadIcon在/Platform SDK/User Interface Services/Resources/Icons中說明,,LoadImage在/Platform SDK/User Interface Services/Resources/Resources中說明),。當然LoadImage更為靈活,,但它沒有LoadIcon簡單。您會注意到,,在ICONDEMO中對同一個圖示呼叫了LoadIcon兩次,。這不會產(chǎn)生問題,也沒有使用額外的內(nèi)存,。LoadIcon是取得句柄但不需要清除句柄的少數(shù)幾個函數(shù)之一,。實際上有一個DestroyIcon函數(shù),但它與CreateIcon,、CreateIconIndirect和CreateIconFromResource連在一起使用,。這些函數(shù)使程序能夠動態(tài)地建立圖標圖像。 使用自訂光標 在程序中使用自訂的鼠標光標與使用自訂的圖示相似,,只是大多數(shù)程序?qū)懽髡呖偸鞘褂肳indows提供的光標,。自訂游標一般為單色,大小為32×32圖素,。在Developer Studio中建立光標與建立圖標的方法相同(從「Insert」菜單上選擇「 Resource」,,然后單擊「Cursor」),但不要忘記定義熱點,。 可以在對象類別定義中設定自訂光標,,敘述為: wndclass.hCursor = LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR)) ; 如果光標用文字名稱定義,則為: wndclass.hCursor = LoadCursor (hInstance, szCursor) ; 每當鼠標位于根據(jù)這個類別建立的窗口上時,,就會顯示與IDC_CURSOR或szCursor相對應的鼠標光標,。 如果使用了子窗口,那么您可能希望光標隨著所在窗口的不同而有所區(qū)別,。如果程序為這些子窗口定義了窗口類別,,就可以在每個窗口類別中適當?shù)卦O定hCursor字段,讓每個窗口類別使用不同的光標,。如果使用了預先定義的子窗口控件,,就可以使用以下方法改變窗口類別的hCursor字段: SetClassLong (hwndChild, GCL_HCURSOR, LoadCursor (hInstance, TEXT ("childcursor")) ; 如果您將顯示區(qū)域劃分為較小的邏輯區(qū)域而不使用子窗口,就可以使用SetCursor來改變鼠標光標: SetCursor (hCursor) ; 在處理WM_MOUSEMOVE消息處理期間,,您應該呼叫SetCursor,;否則,當光標移動時,,Windows將使用窗口類別中定義的光標來重畫光標,。文件指出,如果沒有改變光標,,則SetCursor速度將會很快,。 字符串資源 把字符串當成資源的觀念一開始可能令人覺得詭異。因為我們在使用原始碼中定義為變量的一般字符串時,,并沒有碰到任何問題,。 字符串資源主要是為了讓程序轉(zhuǎn)換成其它語言時更為方便,。正如后面兩章中將看到的一樣,菜單和對話框也是資源描述文件的一部分,。如果使用字符串資源而不是將字符串直接放入原始碼中,,那么程序所使用的所有文字將在同一文件-資源描述檔中。如果轉(zhuǎn)換了資源描述文件中的文字,,那么建立程序的另一種語言版本所需做的一切就是重新連結(jié)程序,。這種方法比重新組織原始碼安全得多(然而,除了下一個范例程序,,我在本書的其它程序中不使用字符串表,,原因是字符串表使程序代碼看起來更為模糊和復雜)。 您可以在「Insert」菜單中選擇「Resource」,,再選擇「 String Table」,,建立一個字符串表。字符串會顯示在屏幕右邊的列表中,。通過雙擊字符串就可以選中它,。針對每個字符串,您可以指定標識符和字符串的內(nèi)容,。 在資源描述中,,字符串顯示在一個多行的敘述中,如下所示: STRINGTABLE DISCARDABLE BEGIN IDS_STRING1, "character string 1" IDS_STRING2, "character string 2" 其它字符串定義 END 如果您在替早期版本的Windows寫程序,,并在文字編輯器中手動建立這個字符串表(用Developer Studio來做這件事當然更容易得多了),,您可以用左右大括號代替BEGIN和END敘述。 資源描述可以包含多個字符串表,,但是每個ID必須唯一表示一個字符串。每個字符串占一行,,最多4097個字符,。\t可以作為制表符,\n則作為linefeed字符號,。DrawText和MessageBox函數(shù)能夠識別這些控制符號,。 您的程序可以使用LoadString呼叫把字符串復制到程序數(shù)據(jù)段的緩沖區(qū)中: LoadString (hInstance, id, szBuffer, iMaxLength) ; 參數(shù)id是ID,它加在資源描述文件中每個字符串的前面,;szBuffer是指向接收字符串的字符數(shù)組的指針,;iMaxLength是送入szBuffer中的最大字符數(shù)。函數(shù)傳回字符串中的字符數(shù),。 每個字符串前面的ID一般是定義在表頭文件中的宏標識符,。許多Windows程序?qū)懽髡呤褂们熬YIDS_ 來表示字符串的ID。有時,,文件名稱或其它信息需要在字符串顯示時插入到字符串中,。在這種情況下,,您可以將C的格式化字符放入字符串,并把它用于wsprintf中作為一個格式化字符串,。 所有資源文字-包括字符串表中的文字-以Unicode格式儲存在.RES編譯資源文件以及最終的.EXE文件中,。LoadStringW函數(shù)直接加載Unicode文字。LoadStringA函數(shù)(僅在Windows 98下有效)完成由Unicode到本地代碼頁的文字轉(zhuǎn)換,。 讓我們來看一個程序,,它使用三個字符串,在消息框中顯示三條錯誤信息,。RESOURCE.H表頭文件為這些信息定義了三個標識符: #define IDS_FILENOTFOUND 1 #define IDS_FILETOOBIG 2 #define IDS_FILEREADONLY 3 資源描述文件具有此字符串表: STRINGTABLE BEGIN IDS_FILENOTFOUND, "File %s not found." IDS_FILETOOBIG, "File %s too large to edit." IDS_FILEREADONLY, "File %s is read-only." END C原始碼文件也包含這個表頭文件,,并定義了一個顯示消息框的函數(shù)(我假定szAppName是一個包含程序名稱的整體變量)。 OkMessage (HWND hwnd, int iErrorNumber, TCHAR *szFileName) { TCHAR szFormat [40] ; TCHAR szBuffer [60] ; LoadString (hInst, iErrorNumber, szFormat, 40) ; wsprintf (szBuffer, szFormat, szFilename) ; return MessageBox ( hwnd, szBuffer, szAppName, MB_OK | MB_ICONEXCLAMATION) ; } 為了顯示包含「file not found」信息的消息框,,程序呼叫: OkMessage (hwnd, IDS_FILENOTFOUND, szFileName) ; 自訂的資源 Windows也定義了「自訂資源」,,這又稱為「使用者定義的資源」(使用者就是您-程序?qū)懽髡撸皇悄莻€使用您程序的幸運者),。自訂資源讓連結(jié).EXE文件中的各種數(shù)據(jù)更為方便,,對取得程序中的數(shù)據(jù)也是如此。資料可以是您需要的任何格式,。程序用于存取自訂資源的Windows函數(shù)促使Windows將數(shù)據(jù)加載內(nèi)存并傳回指向它的指標,。然后您就可以對程序做任何操作。您會發(fā)現(xiàn)對于儲存和存取各種自己的數(shù)據(jù),,這要比把數(shù)據(jù)儲存在外部文件中,,再使用文件輸入函數(shù)存取它要方便得多。 例如,,您有一個文件叫做BINDATA.BIN,,它包含程序需要顯示的一些數(shù)據(jù)。您可以選擇這個文件的格式,。如果在MYPROG項目中有MYPROG.RC資源描述檔,,您就可以在Developer Studio中從「Insert」菜單中選擇「Resource」并按「 Custom」按鈕,來建立自訂的資源,。鍵入表示資源的名稱:例如,,BINTYPE。然后,,Developer Studio會生成資源名稱(在這種情況下是IDR_BINTYPE1)并顯示讓您輸入二進制數(shù)據(jù)的窗口,。但是您不必輸入什么,用鼠標右鍵單擊IDR_BINTYPE1名稱,,并選擇 Properties,,然后就可以輸入一個文件名稱:例如,BINDATA.BIN,。 資源描述檔就會包含以下的一行敘述: IDR_BINTYPE1 BINTYPE BINDATA.BIN 除了我們剛剛生成的BINTYPET資源型態(tài)外,,這個敘述與ICONDEMO中的ICON敘述一樣。有了圖示后,,您可以對資源名稱使用文字的名稱,,而不是數(shù)字的標識符。 當您編譯并連結(jié)程序,,整個BINDATA.BIN文件會被并入MYPROG.EXE文件中,。 在程序的初始化(比如,在處理WM_CREATE消息時)期間,,您可以獲得資源的句柄: hResource = LoadResource (hInstance, FindResource (hInstance, TEXT ("BINTYPE"), MAKEINTRESOURCE (IDR_BINTYPE1))) ; 變量hResource定義為HGLOBAL型態(tài),,它是指向內(nèi)存區(qū)塊的句柄。不管它的名稱是什么,,LoadResource不會立即將資源加載內(nèi)存,。把LoadResource和FindResource函數(shù)如上例般合在一起使用,在實質(zhì)上就類似于LoadIcon和LoadCursor函數(shù)的做法,。事實上,,LoadIcon和LoadCursor函數(shù)就用到了LoadResource和FindResource函數(shù),。 當您需要存取文字時,,呼叫LockResource: pData = LockResource (hResource) ; LockResource將資源加載內(nèi)存(如果還沒有加載的話),,然后它會傳回一個指向資源的指標。當結(jié)束對資源的使用時,,您可以從內(nèi)存中釋放它: FreeResource (hResource) ; 當您的程序終止時,也會釋放資源,,即使您沒有呼叫FreeResource.,。 讓我們看一個使用三種資源-一個圖標,、一個字符串表和一個自訂的資源-的范例程序。程序10-3所示的POEPOEM程序在其顯示區(qū)域顯示Edgar Allan Poe的「Annabel Lee」文字,。自訂的資源是文件POEPOEM.TXT,,它包含了一段詩文,此文本文件以反斜線(\)結(jié)束,。 程序10-3 POEPOEM POEPOEM.C /*--------------------------------------------------------------------------- POEPOEM.C -- Demonstrates Custom Resource (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; HINSTANCE hInst ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { TCHAR szAppName [16], szCaption [64], szErrMsg [64] ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; LoadString ( hInstance, IDS_APPNAME, szAppName, sizeof (szAppName) / sizeof (TCHAR)) ; LoadString ( hInstance, IDS_CAPTION, szCaption, sizeof (szCaption) / sizeof (TCHAR)) ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { LoadStringA (hInstance, IDS_APPNAME, (char *) szAppName, sizeof (szAppName)) ; LoadStringA (hInstance, IDS_ERRMSG, (char *) szErrMsg, sizeof (szErrMsg)) ; MessageBoxA (NULL, (char *) szErrMsg, (char *) szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, szCaption, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static char * pText ; static HGLOBAL hResource ; static HWND hScroll ; static int iPosition, cxChar, cyChar, cyClient, iNumLines, xScroll ; HDC hdc ; PAINTSTRUCT ps ; RECT rect ; TEXTMETRIC tm ; switch (message) { case WM_CREATE : hdc = GetDC (hwnd) ; GetTextMetrics (hdc, &tm) ; cxChar = tm.tmAveCharWidth ; cyChar = tm.tmHeight + tm.tmExternalLeading ; ReleaseDC (hwnd, hdc) ; xScroll = GetSystemMetrics (SM_CXVSCROLL) ; hScroll = CreateWindow (TEXT ("scrollbar"), NULL, WS_CHILD | WS_VISIBLE | SBS_VERT, 0, 0, 0, 0, hwnd, (HMENU) 1, hInst, NULL) ; hResource = LoadResource (hInst, FindResource (hInst, TEXT ("AnnabelLee"), TEXT ("TEXT"))) ; pText = (char *) LockResource (hResource) ; iNumLines = 0 ; while (*pText != '\\' && *pText != '\0') { if (*pText == '\n') iNumLines ++ ; pText = AnsiNext (pText) ; } *pText = '\0' ; SetScrollRange (hScroll, SB_CTL, 0, iNumLines, FALSE) ; SetScrollPos (hScroll, SB_CTL, 0, FALSE) ; return 0 ; case WM_SIZE : MoveWindow (hScroll, LOWORD (lParam) - xScroll, 0, xScroll, cyClient = HIWORD (lParam), TRUE) ; SetFocus (hwnd) ; return 0 ; case WM_SETFOCUS : SetFocus (hScroll) ; return 0 ; case WM_VSCROLL : switch (wParam) { case SB_TOP : iPosition = 0 ; break ; case SB_BOTTOM : iPosition = iNumLines ; break ; case SB_LINEUP : iPosition -= 1 ; break ; case SB_LINEDOWN : iPosition += 1 ; break ; case SB_PAGEUP : iPosition -= cyClient / cyChar ; break ; case SB_PAGEDOWN : iPosition += cyClient / cyChar ; break ; case SB_THUMBPOSITION : iPosition = LOWORD (lParam) ; break ; } iPosition = max (0, min (iPosition, iNumLines)) ; if (iPosition != GetScrollPos (hScroll, SB_CTL)) { SetScrollPos (hScroll, SB_CTL, iPosition, TRUE) ; InvalidateRect (hwnd, NULL, TRUE) ; } return 0 ; case WM_PAINT : hdc = BeginPaint (hwnd, &ps) ; pText = (char *) LockResource (hResource) ; GetClientRect (hwnd, &rect) ; rect.left += cxChar ; rect.top += cyChar * (1 - iPosition) ; DrawTextA (hdc, pText, -1, &rect, DT_EXTERNALLEADING) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY : FreeResource (hResource) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } POEPOEM.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // TEXT ANNABELLEE TEXT DISCARDABLE "poepoem.txt" ///////////////////////////////////////////////////////////////////////////// // Icon POEPOEM ICON DISCARDABLE "poepoem.ico" ///////////////////////////////////////////////////////////////////////////// // String Table STRINGTABLE DISCARDABLE BEGIN IDS_APPNAME "PoePoem" IDS_CAPTION """Annabel Lee"" by Edgar Allan Poe" IDS_ERRMSG "This program requires Windows NT!" END RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by PoePoem.rc #define IDS_APPNAME 1 #define IDS_CAPTION 2 #define IDS_ERRMSG 3 POEPOEM.TXT It was many and many a year ago, In a kingdom by the sea, That a maiden there lived whom you may know By the name of Annabel Lee; And this maiden she lived with no other thought Than to love and be loved by me. I was a child and she was a child In this kingdom by the sea, But we loved with a love that was more than love -- I and my Annabel Lee -- With a love that the winged seraphs of Heaven Coveted her and me. And this was the reason that, long ago, In this kingdom by the sea, A wind blew out of a cloud, chilling My beautiful Annabel Lee; So that her highborn kinsmen came And bore her away from me, To shut her up in a sepulchre In this kingdom by the sea. The angels, not half so happy in Heaven, Went envying her and me -- Yes! that was the reason (as all men know, In this kingdom by the sea) That the wind came out of the cloud by night, Chilling and killing my Annabel Lee. But our love it was stronger by far than the love Of those who were older than we -- Of many far wiser than we -- And neither the angels in Heaven above Nor the demons down under the sea Can ever dissever my soul from the soul Of the beautiful Annabel Lee: For the moon never beams, without bringing me dreams Of the beautiful Annabel Lee; And the stars never rise, but I feel the bright eyes Of the beautiful Annabel Lee: And so, all the night-tide, I lie down by the side Of my darling -- my darling -- my life and my bride, In her sepulchre there by the sea -- In her tomb by the sounding sea. [May, 1849]
在POEPOEM.RC資源描述檔中,,使用者定義的資源被定義為TEXT型態(tài),取名為AnnabelLee: ANNABELLEE TEXT POEPOEM.TXT 在WndProc處理WM_CREATE時,,使用FindResource和LoadResource取得資源句柄,。使用LockResource鎖定資源,并且使用一個小程序?qū)⑽募┪驳姆葱本€(\)換成0,,這有利于后面WM_PAINT消息處理期間使用的DrawText函數(shù),。 注意,這里使用的是子窗口的滾動條,,而不是窗口滾動條,,這是因為子窗口滾動條有一個自動的鍵盤接口,因此在POEPOEM中沒有處理WM_KEYDOWN,。 POEPOEM還使用三個字符串,,它們的ID在RESOURCE.H表頭文件中定義。在程序的開始,,IDS_APPNAME和IDS_CAPTIONPOEPOEM字符串由LoadString加載內(nèi)存: LoadString (hInstance, IDS_APPNAME, szAppName, sizeof (szAppName) / sizeof (TCHAR)) ; LoadString (hInstance, IDS_CAPTION, szCaption, sizeof (szCaption) / sizeof (TCHAR)) ; 注意RegisterClass前面的兩個呼叫,。如果您在Windows 98下執(zhí)行Unicode版本的POEPOEM,這兩個呼叫就都會失敗,。因此,,LoadStringA比LoadStringW要復雜得多(LoadStringA必須將資源字符串由Unicode轉(zhuǎn)化為ANSI,而LoadStringW僅是直接加載它),,LoadStringW在Windows 98下不被支持,。這意味著在Windows 98下,當RegisterClassW函數(shù)失敗時,,MessageBoxW函數(shù)(Windows 98支持)就不能使用LoadStringW加載程序的字符串,。由于這個原因,程序使用LoadStringA加載IDS_APPNAME和IDS_ERRMSG字符串,,并使用MessageBoxA顯示自訂的消息框: if (!RegisterClass (&wndclass)) { LoadStringA (hInstance, IDS_APPNAME, (char *) szAppName, sizeof (szAppName)) ; LoadStringA (hInstance, IDS_ERRMSG, (char *) szErrMsg, sizeof (szErrMsg)) ; MessageBoxA (NULL, (char *) szErrMsg, (char *) szAppName, MB_ICONERROR) ; return 0 ; } 注意,,TCHAR字符串變量是指向char的指針。 既然我們已經(jīng)定義了用于POEPOEM的所有字符串資源,,那么翻譯者將程序轉(zhuǎn)換成外語版本就很容易了,。當然,它們將不得不翻譯「Annabel Lee」這個名字-我想,這會是一項困難得多的工作,。 您還記得Monty Python有關(guān)奶酪店的幽默短劇嗎,?那故事內(nèi)容是這樣的:一個客人走進奶酪店想買某種奶酪。當然,,店里沒有這種奶酪,。因此他又問有沒有另一種奶酪,然后再問另一種,,再問另一種,,不斷的問店家有沒有另一種奶酪(最后總共問了40種的奶酪),回答仍然是沒有,,沒有,,沒有,沒有,,沒有,。 這個不幸的事件可以通過菜單的使用來避免。一個菜單是一列可用的選項,,它告訴饑餓的用餐者,,廚房可以提供哪些服務,并且-對于Windows程序來說-還告訴使用者一個應用程序能夠執(zhí)行哪些操作,。 菜單可能是Windows程序提供的一致使用者接口中最重要的部分,,而在您的程序中增加菜單,是Windows程序設計中相對簡單的部分,。您在Developer Studio中定義菜單,。每個可選的菜單項被賦予唯一的ID。您在窗口類別結(jié)構(gòu)中指定菜單名稱,。當使用者選擇一個菜單項時,,Windows給您的程序發(fā)送包含該ID的WM_COMMAND消息。 討論完菜單后,,我還將討論鍵盤快捷鍵,,它們是一些鍵的組合,主要用于啟動菜單功能,。 菜單概念 窗口的菜單列緊接在標題列的下方顯示,,這個菜單列有時被稱為「主菜單」或「頂層菜單」。列在頂層菜單的項目通常是下拉式菜單,,也叫做「彈出式菜單」或「子菜單」,。您也可以定義多重嵌套的彈出式菜單,也就是說,,在彈出式菜單上的項目可以存取另一個彈出式菜單,。有時彈出式菜單上的項目呼叫對話框以獲得更多的信息(對話框在下一章介紹)。在標題列的最左端,,很多父窗口都顯示程序的小圖標,,這個圖標可以啟動系統(tǒng)菜單。它實際上是另一個彈出式菜單,。 彈出式菜單的各項可以是「被選中的」,,這意味著Windows在菜單文字的左端顯示一個小的選中標記,選中標記讓使用者知道從菜單中選中了哪些選項,。這些選項之間可以是互斥的,,也可以不互斥。頂層菜單項不能被選中,。 頂層菜單或彈出式菜單項可以被「啟用」,、「禁用」或「無效化」?!竼印购汀覆粏印褂袝r候被當作「啟用」和「禁用」的同義詞,。被啟用或禁用的菜單項在使用者看來是一樣的,但是無效化的菜單項是使用灰色文字來顯示的,。 從使用者的角度來看,,啟用、禁用和無效化的菜單項都是可以「選擇的」(被選擇的菜單項目會被加高亮度顯示),,也就是說,,使用者可以使用鼠標選擇被禁用的菜單項,將反相顯示光標列移動到禁用的菜單項上,,或者使用菜單項的關(guān)鍵詞母來選擇該菜單項,。然而,從程序?qū)懽髡叩慕嵌葋砜?,啟用,、禁用和無效化菜單項的功能是不同的。Windows只為啟用的菜單項向程序發(fā)送WM_COMMAND消息,。要讓選項變得無效,,可以把那些菜單項禁用和無效化。如果您想讓使用者知道選擇是無效的,,那么您可以讓一個菜單項無效化,。 菜單結(jié)構(gòu) 當您建立或改變程序中的菜單時,把頂層菜單和每一個彈出式菜單想象成各自獨立的菜單是有用的,。頂層菜單有一個菜單句柄,,在頂層菜單中的每一個彈出式菜單也有它自己的菜單句柄。系統(tǒng)菜單(也是一個彈出式菜單)也有菜單句柄,。 菜單中的每一項都有三個特性,。第一個特性是菜單中顯示什么,它可以是字符串或位圖。第二個特性是WM_COMMAND消息中Windows發(fā)送給程序的菜單ID,,或者是在使用者選擇菜單項時Windows顯示的彈出式菜單的句柄,。第三個特性是菜單項的屬性,包括是否被禁用,、無效化或被選中,。 定義菜單 要使用Developer Studio來給程序資源描述文件添加菜單,可以從Insert菜單中選擇 Resource并選擇Menu(或者您可能已經(jīng)知道了),。然后,,您可以用交談式的方式定義菜單。菜單中每一項都有一個相關(guān)的 Menu Item Properties對話框,,指出該項目的字符串,。如果選中了Pop-up復選框,該項目就會呼叫一個彈出式菜單,,并且沒有ID與此項目相聯(lián)系,。如果沒有選中 Pop-up復選框,該項目被選中時就會產(chǎn)生帶有特定ID的WM_COMMAND消息,。這兩類菜單項分別出現(xiàn)在資源描述檔的POPUP和MENUITEM敘述中,。 當您為菜單中的項目鍵入文字時,可以鍵入一個「&」符號,,指出后面一個字符在Windows顯示菜單時要加底線,。這種底線字符是在您使用Alt鍵選擇菜單項時Windows要尋找的比對字符。如果在文字中不包括「&」符號,,就不顯示任何底線,,Windows會將菜單項文字的第一個字母用于Alt鍵查找。 如果在Menu Items Properties對話框中選中Grayed選項,,則菜單項是不能啟動的,,它的文字是灰色的,該項不產(chǎn)生WM_COMMAND消息,。如果選中 Inactive選項,,則菜單項也是不能啟動的,也不產(chǎn)生WM_COMMAND消息,,但是它的文字顯示正常,。 Checked選項在菜單項邊上放置一個選中標記。Separator選項在彈出式菜單上產(chǎn)生一個分欄的橫線,。 在彈出式菜單的項目上,,可以在字符串中使用制表符\t。緊接著\t的文字被放置在距離彈出式菜單的第一列右邊新的一列上,。在本章后面,,會看到在使用鍵盤快捷鍵時它起的作用,。字符串中的\a使跟著它的文字向右對齊。 您指定的ID值是Windows發(fā)送給窗口消息處理程序中菜單消息中的數(shù)值,。在菜單中ID值應該是唯一的,。按照慣例,我使用以IDM(「ID for a Menu」)開頭的標識符,。 在程序中引用菜單 大多數(shù)Windows應用程序在資源描述文件中只有一個菜單。您可以給菜單起一個與程序名稱相同的文字的名稱,。程序?qū)懽髡呓?jīng)常將程序名用于菜單名稱,,以便相同的字符串可以用于窗口類別、程序的圖標名稱和菜單名稱,。然后,,程序在窗口的定義中為菜單引用該名稱: wndclass.lpszMenuName = szAppName ; 雖然存取菜單資源的最常用方法是在窗口類別中指定菜單,您也可以使用其它方法,。Windows應用程序可以使用LoadMenu函數(shù)將菜單資源加載內(nèi)存中,,如同LoadIcon和LoadCursor函數(shù)一樣。LoadMenu傳回一個菜單句柄,。如果您在資源描述檔中為菜單使用了名稱,,敘述如下: hMenu = LoadMenu (hInstance, TEXT ("MyMenu")) ; 如果使用了數(shù)值,那么LoadMenu呼叫采用如下的形式: hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (ID_MENU)) ; 然后,,您可以將這個菜單句柄作為CreateWindow的第九個參數(shù): hwnd = CreateWindow ( TEXT ("MyClass"), TEXT ("Window Caption"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL) ; 在這種情況下,,CreateWindow呼叫中指定的菜單可以覆蓋窗口類別中指定的任何菜單。如果CreateWindow的第九個參數(shù)是NULL,,那么您可以把窗口類別中的菜單看作是這種窗口類別的窗口內(nèi)定使用的菜單,。這樣,您可以為依據(jù)同一窗口類別建立的幾個窗口使用不同的菜單,。 您也可以在窗口類別中指定NULL菜單,,并且在CreateWindow呼叫中也指定NULL菜單,然后在窗口被建立后再給窗口指定一個菜單: SetMenu (hwnd, hMenu) ; 這種形式使您可以動態(tài)地修改窗口的菜單,。在本章后面的NOPOPUPS程序中我們將會看到這方面的例子,。 當窗口被清除時,與窗口相關(guān)的所有菜單都將被清除,。與窗口不相關(guān)的菜單在程序結(jié)束前通過呼叫DestroyMenu主動清除,。 菜單和消息 當使用者選擇一個菜單項時,Windows通常向窗口消息處理程序發(fā)送幾個不同的消息,。在大多數(shù)情況下,, 您的程序可以忽略大部分消息,只需把它們傳遞給DefWindowProc即可,。WM_INITMENU就是這一類的消息,,它具有下列參數(shù): wParam: 主菜單句柄 lParam: 0 wParam值是您的主菜單句柄,,即使使用者選擇的是系統(tǒng)菜單中的項目。Windows程序通常忽略WM_INITMENU消息,。盡管在選中該項之前的消息已經(jīng)給程序提供了修改菜單的機會,,但是我們覺得此刻改變頂層菜單是會擾亂使用者的。 程序也會接收到WM_MENUSELECT消息,。隨著使用者在菜單項中移動光標或者鼠標,,程序會收到許多WM_MENUSELECT消息。這對實作那些包含對菜單項的文字描述的狀態(tài)列是很有幫助的,。WM_MENUSELECT的參數(shù)如下所示: LOWORD (wParam):被選中項目:菜單ID或者彈出式菜單句柄 HIWORD (wParam):選擇旗標 lParam: 包含被選中項目的菜單句柄 WM_MENUSELECT是一個菜單追蹤消息,,wParam的值告訴您目前選擇的是菜單中的哪一項(加高亮度顯示的那個),wParam的高字組中的「選擇旗標」可以是下列這些旗標的組合:MF_GRAYED,、MF_DISABLED,、MF_CHECKED、MF_BITMAP,、MF_POPUP,、MF_HELP、MF_SYSMENU和MF_MOUSESELECT,。如果您需要根據(jù)對菜單項的選擇來改變窗口顯示區(qū)域的內(nèi)容,,那么您可以使用WM_MENUSELECT消息。許多程序把該消息發(fā)送給DefWindowProc,。 當Windows準備顯示一個彈出式菜單時,,它給窗口消息處理程序發(fā)送一個WM_INITMENUPOPUP消息,參數(shù)如下: wParam: 彈出式菜單句柄 LOWORD (lParam):彈出式菜單索引 HIWORD (lParam): 系統(tǒng)菜單為1,,其它為0 如果您需要在顯示彈出式菜單之前啟用或者禁用菜單項,,那么這個消息就很重要。例如,,假定程序使用彈出式菜單上的 Paste命令從剪貼簿復制文字,,當您收到彈出式菜單中的WM_INITMENUPOPUP消息時,應確定剪貼簿內(nèi)是否有文字存在,。如果沒有,,那么應該使 Paste菜單項無效化。我們將在本章后面修改的POPPAD程序中看到這樣的例子,。 最重要的菜單消息是WM_COMMAND,,它表示使用者已經(jīng)從菜單中選中了一個被啟用的菜單項。第八章中的WM_COMMAND消息也可以由子窗口控件產(chǎn)生,。如果您碰巧為菜單和子窗口控件使用同一ID碼,,那么您可以通過lParam的值來區(qū)別它們,菜單項的lParam其值為0,,請參見表10-1,。
WM_SYSCOMMAND消息類似于WM_COMMAND消息,,只是WM_SYSCOMMAND表示使用者從系統(tǒng)菜單中選擇一個啟用的菜單項: wParam: 菜單ID lParam: 0 然而,如果WM_SYSCOMMAND消息是由按鼠標按鍵產(chǎn)生的,,LOWORD(lParam)和HIWORD(lParam)將包含鼠標光標位置的x和y屏幕坐標,。 對于WM_SYSCOMMAND,菜單ID指示系統(tǒng)菜單中的哪一項被選中,。對于預先定義的系統(tǒng)菜單項,,較低的那四個位應該和0xFFF0進行AND運算來屏蔽掉,結(jié)果值應該為下列之一:SC_SIZE,、SC_MOVE,、SC_MINIMIZE、SC_MAXIMIZE,、SC_NEXTWINDOW,、SC_PREVWINDOW,、SC_CLOSE,、SC_VSCROLL、SC_HSCROLL,、SC_ARRANGE,、SC_RESTORE和SC_TASKLIST。此外,,wParam可以是SC_MOUSEMENU或SC_KEYMENU,。 如果您在系統(tǒng)菜單中添加菜單項,那么wParam的低字組將是您定義的菜單ID,。為了避免與預先定義的菜單ID相沖突,,應用程序應該使用小于0xF000的值,這對于將一般的WM_SYSCOMMAND消息發(fā)送給DefWindowProc是很重要的,。如果您不這樣做,,那么您實際上就是禁用了正常的系統(tǒng)菜單命令。 我們將討論的最后一個消息是WM_MENUCHAR,。實際上,,它根本不是菜單消息。在下列兩種情況之一發(fā)生時,,Windows會把這個消息發(fā)送到窗口消息處理程序:如果使用者按下Alt和一個與菜單項不匹配的字符時,,或者在顯示彈出式菜單而使用者按下一個與彈出式菜單里的項目不匹配的字符鍵時。隨WM_MENUCHAR消息一起發(fā)送的參數(shù)如下所示: LOWORD (wParam): 字符代碼(ASCII或Unicode) HIWORD (wParam): 選擇碼 lParam: 菜單句柄 選擇碼是:
Windows程序通常把該消息傳遞給DefWindowProc,,它一般給Windows傳回0,,這會使Windows發(fā)出嗶聲。在 第十四章GRAFMENU程序中會看到WM_MENUCHAR消息的使用,。 范例程序 讓我們來看一個簡單的例子,。程序10-4所示的MENUDEMO程序,,在主菜單中有五個選擇項-File、Edit,、Background,、Timer和Help,每一項都與一個彈出式菜單相連,。MENUDEMO只完成了最簡單,、最通用的菜單處理操作,包括攔截WM_COMMAND消息和檢查wParam的低字組,。 程序10-4 MENUDEMO
MENUDEMO.C /*--------------------------------------------------------------------- MENUDEMO.C -- Menu Demonstration (c) Charles Petzold, 1998 -----------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" #define ID_TIMER 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("MenuDemo") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("Menu Demonstration"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static int idColor [5] = { WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH } ; static int iSelection = IDM_BKGND_WHITE ; HMENU hMenu ; switch (message) { case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: MessageBeep (0) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; case IDM_EDIT_UNDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: MessageBeep (0) ; return 0 ; case IDM_BKGND_WHITE: // Note: Logic below case IDM_BKGND_LTGRAY: // assumes that IDM_WHITE case IDM_BKGND_GRAY: // through IDM_BLACK are case IDM_BKGND_DKGRAY: // consecutive numbers in case IDM_BKGND_BLACK: // the order shown here. CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ; iSelection = LOWORD (wParam) ; CheckMenuItem (hMenu, iSelection, MF_CHECKED) ; SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) GetStockObject (idColor [LOWORD (wParam) - IDM_BKGND_WHITE])) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_TIMER_START: if (SetTimer (hwnd, ID_TIMER, 1000, NULL)) { EnableMenuItem (hMenu, IDM_TIMER_START, MF_GRAYED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_ENABLED) ; } return 0 ; case IDM_TIMER_STOP: KillTimer (hwnd, ID_TIMER) ; EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_GRAYED) ; return 0 ; case IDM_APP_HELP: MessageBox (hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_APP_ABOUT: MessageBox (hwnd,TEXT ("Menu Demonstration Program\n") TEXT ("(c) Charles Petzold, 1998"), szAppName, MB_ICONINFORMATION | MB_OK) ; return 0 ; } break ; case WM_TIMER: MessageBeep (0) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } MENUDEMO.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu MENUDEMO MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "C&ut", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR END POPUP "&Background" BEGIN MENUITEM "&White", IDM_BKGND_WHITE, CHECKED MENUITEM "&Light Gray", IDM_BKGND_LTGRAY MENUITEM "&Gray", IDM_BKGND_GRAY MENUITEM "&Dark Gray", IDM_BKGND_DKGRAY MENUITEM "&Black", IDM_BKGND_BLACK END POPUP "&Timer" BEGIN MENUITEM "&Start", IDM_TIMER_START MENUITEM "S&top", IDM_TIMER_STOP, GRAYED END POPUP "&Help" BEGIN MENUITEM "&Help...", IDM_APP_HELP MENUITEM "&About MenuDemo...", IDM_APP_ABOUT END END RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by MenuDemo.rc #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_SAVE 40003 #define IDM_FILE_SAVE_AS 40004 #define IDM_APP_EXIT 40005 #define IDM_EDIT_UNDO 40006 #define IDM_EDIT_CUT 40007 #define IDM_EDIT_COPY 40008 #define IDM_EDIT_PASTE 40009 #define IDM_EDIT_CLEAR 40010 #define IDM_BKGND_WHITE 40011 #define IDM_BKGND_LTGRAY 40012 #define IDM_BKGND_GRAY 40013 #define IDM_BKGND_DKGRAY 40014 #define IDM_BKGND_BLACK 40015 #define IDM_TIMER_START 40016 #define IDM_TIMER_STOP 40017 #define IDM_APP_HELP 40018 #define IDM_APP_ABOUT 40019 MENUDEMO.RC資源描述檔給了您定義菜單的提示,。菜單的名稱為「MenuDemo」。大多數(shù)項目有底線字母,,這就是說您必須在字母前鍵入『&』,。MENUITEM SEPARATOR敘述是在「Menu Item Properties」對話框中選中「Separator」框產(chǎn)生的。注意菜單中有一個項目具有「 Checked」選項,,另一個具有「Grayed」選項,。還有,「 Background」彈出式菜單中的五個項目應該按順序輸入,,確保標識符是以數(shù)值的順序,,本程序需要這樣。所有菜單項的標識符定義在RESOURCE.H中,。 當收到彈出式菜單「File」和「Edit」各項有關(guān)的WM_COMMAND消息時,,MENUDEMO程序只使系統(tǒng)發(fā)出嗶聲?!?b> Background」彈出式菜單列出MENUDEMO用來給背景著色的五種現(xiàn)有畫刷,。在MENUDEMO.RC資源描述檔中,「 White」菜單項(菜單ID為IDM_BKGND_WHITE)被標以「 CHECKED」,,它在菜單項旁邊設定選中標記,。在MENUDEMO.C中,iSelection的值被初始化為IDM_BKGND_WHITE,。 「Background」彈出式菜單上的五種畫刷相互排斥,。當MENUDEMO.C收到一個WM_COMMAND消息,而該消息中的wParam是「 Background」彈出式菜單上的五項之一時,,它必須從先前選中的背景顏色中除掉選中標記,,并把標記加到新的背景顏色上。為此,,首先要得到菜單句柄: hMenu = GetMenu (hwnd) ; CheckMenuItem函數(shù)用來取消目前被選中的項目: CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ; iSelection的值被設定為wParam的值,,新的背景顏色被選中: iSelection = wParam ; CheckMenuItem (hMenu, iSelection, MF_CHECKED) ; 窗口類別中的背景顏色于是被替換為新的背景顏色,窗口顯示區(qū)域變?yōu)闊o效狀態(tài),Windows使用新的背景顏色清除窗口,。 Timer彈出式菜單列出了兩個選項-「Start」和「Stop」,。開始時,「Stop」選項變?yōu)榛疑模ň拖裨谫Y源描述檔中的菜單定義一樣),。當您選擇「Start」選項時,,MENUDEMO試圖啟動一個定時器,如果成功,,則無效化「Start」選項,,并啟用「Stop」選項: EnableMenuItem (hMenu, IDM_TIMER_START, MF_GRAYED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_ENABLED) ; 當收到一條WM_COMMAND消息,并且wParam等于IDM_TIMER_STOP時,,MENUDEMO程序會停止計數(shù),,啟用「 Start」項,然后無效化「Stop」選項: EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_GRAYED) ; 請注意,,在定時器執(zhí)行時,,MENUDEMO程序不可能收到wParam等于IDM_TIMER_START的WM_COMMAND消息。同樣地,,在定時器關(guān)閉時,,MENUDEMO程序也不可能收到wParam等于IDM_TIMER_STOP的WM_COMMAND消息。 當MENUDEMO收到一個WM_COMMAND消息,,而該消息的參數(shù)wParam等于IDM_APP_ABOUT或IDM_APP_HELP時,,MENUDEMO程序顯示一個消息框(在下一章中,,我們將把消息框變?yōu)閷υ捒颍?/p> 當MENUDEMO程序收到一個WM_COMMAND消息,,其參數(shù)wParam等于IDM_APP_EXIT時,它給自己發(fā)送一個WM_CLOSE消息,。這個消息與DefWindowProc收到WM_SYSCOMMAND消息且wParam等于SC_CLOSE時發(fā)送給窗口消息處理程序的消息相同,。我們將在本章后面介紹 POPPAD2時再仔細研究這個問題。 菜單設計規(guī)范 在MENUDEMO中的「File」和「Edit」彈出式菜單的格式與其它Windows程序中的格式非常類似,。Windows的目的之一是為使用者提供一種易懂的接口,,而不要求使用者為每個程序重新學習基本操作方式。如果「 File」和「Edit」菜單在每個Windows程序中看起來都一樣,,并且都使用同樣的字母和Alt鍵來進行選擇,,那么當然有助于減輕使用者的學習負擔。 除了「File」和「Edit」彈出式菜單外,,大多數(shù)Windows程序的菜單都是不同的,。當設計一個菜單時,您應該看一看現(xiàn)有的Windows程序以盡量保持一致,。當然,,如果您認為別的程序是不對的,而您知道正確的方法,,那么沒有人能夠阻止您,。同時記住,,修改一個菜單,通常只需要修改資源描述檔而不必修改您的程序代碼,。即使以后要改變菜單項的位置,,也不會有多大的問題。 雖然您的程序菜單在頂層可以有MENUITEM敘述,,但這是不合規(guī)范的,,因為這樣會很容易導致錯誤的選擇。如果您要這樣做,,那么請在字符串后面加一個驚嘆號,,表示菜單項不會啟動彈出式菜單。 較難的一種菜單定義方法 在程序的資源描述文件中定義菜單,,通常是在您的窗口中添加菜單的最簡單方法,,但不是唯一的方法。如果您沒有使用資源描述檔,,那么可以使用CreateMenu和AppendMenu兩個函數(shù)在程序中建立菜單,。在您定義完菜單后,您可以將菜單句柄發(fā)送給CreateWindow,,或者使用SetMenu來設定窗口的菜單,。 以下是具體的做法。CreateMenu簡單地把一個句柄傳回給新菜單: hMenu = CreateMenu () ; 菜單一開始為空,。AppendMenu將菜單項插入菜單中,。您必須為頂層菜單項和每一個彈出式菜單提供不同的菜單句柄。彈出式菜單是單獨構(gòu)成的,,然后將彈出式菜單句柄插入頂層菜單,。程序10-5中所示的程序代碼就是用這種方法建立菜單的,實際上,,這個菜單與MENUDEMO程序中的菜單相同,。為了簡化說明,代碼使用ASCII字符串,。 程序10-5 不使用資源描述文件建立與MENUDEMO程序相同菜單的C程序代碼 hMenu = CreateMenu () ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_NEW, "&New"); AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_OPEN, "&Open..."); AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE, "&Save"); AppendMenu (hMenuPopup, MF_STRING, IDM_FILE_SAVE_AS, "Save &As..."); AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_EXIT, "E&xit") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&File") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_EDIT_UNDO,"&Undo") ; AppendMenu (hMenuPopup, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenuPopup, MF_STRING,IDM_EDIT_CUT, "Cu&t") ; AppendMenu (hMenuPopup, MF_STRING,IDM_EDIT_COPY,"&Copy") ; AppendMenu (hMenuPopup, MF_STRING,IDM_EDIT_PASTE,"&Paste") ; AppendMenu (hMenuPopup, MF_STRING,IDM_EDIT_CLEAR,"De&lete") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Edit") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING| MF_CHECKED, IDM_BKGND_WHITE, "&White"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_LTGRAY, "&Light Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_GRAY, "&Gray") ; AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_DKGRAY, "&Dark Gray"); AppendMenu (hMenuPopup, MF_STRING, IDM_BKGND_BLACK, "&Black") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Background") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_TIMER_START, "&Start") ; AppendMenu (hMenuPopup, MF_STRING | MF_GRAYED, IDM_TIMER_STOP, "S&top") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Timer") ; hMenuPopup = CreateMenu () ; AppendMenu (hMenuPopup, MF_STRING, IDM_HELP_HELP, "&Help") ; AppendMenu (hMenuPopup, MF_STRING, IDM_APP_ABOUT, "&About MenuDemo...") ; AppendMenu (hMenu, MF_POPUP, hMenuPopup, "&Help") ; 我認為您會同意底下這個觀點:使用資源描述檔菜單模板來制作菜單,,會更容易而且更清楚。我并不鼓勵您使用這里的方法定義菜單,,而只是提供了一種實作菜單的方法,。當然,您可以使用包含所有菜單項字符串,、ID和旗標等的結(jié)構(gòu)數(shù)組來壓縮程序代碼大小,。不過,如果您這么做了,那么您還可以利用Windows定義菜單的第三種方法,。LoadMenuIndirect函數(shù)接受一個指向MENUITEMTEMPLATE型態(tài)的結(jié)構(gòu)指針,,并傳回菜單的句柄,該函數(shù)在載入資源描述檔中的常規(guī)菜單模板后,,在Windows中構(gòu)造菜單,,讀者不妨自己嘗試一下。 浮動彈出式菜單 您還可以在沒有頂層菜單列的情況下使用菜單,,也就是說,,您可以使彈出式菜單出現(xiàn)在屏幕頂層的任何位置。一種方法是使用鼠標右鍵來啟動彈出式菜單,。程序10-6所示的POPMENU說明了這種方法,。 程序10-6 POPMENU
POPMENU.C /*---------------------------------------------------------------------- POPMENU.C -- Popup Menu Demonstration (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; HINSTANCE hInst ; TCHAR szAppName[] = TEXT ("PopMenu") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hInst = hInstance ; hwnd = CreateWindow ( szAppName, TEXT ("Popup Menu Demonstration"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HMENU hMenu ; static int idColor [5] = {WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH } ; static int iSelection = IDM_BKGND_WHITE ; POINT point ; switch (message) { case WM_CREATE: hMenu = LoadMenu (hInst, szAppName) ; hMenu = GetSubMenu (hMenu, 0) ; return 0 ; case WM_RBUTTONUP: point.x = LOWORD (lParam) ; point.y = HIWORD (lParam) ; ClientToScreen (hwnd, &point) ; TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: case IDM_EDIT_UNDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: MessageBeep (0) ; return 0 ; case IDM_BKGND_WHITE: // Note: Logic below case IDM_BKGND_LTGRAY: // assumes that IDM_WHITE case IDM_BKGND_GRAY: // through IDM_BLACK are case IDM_BKGND_DKGRAY: // consecutive numbers in case IDM_BKGND_BLACK: // the order shown here. CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ; iSelection = LOWORD (wParam) ; CheckMenuItem (hMenu, iSelection, MF_CHECKED) ; SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) GetStockObject (idColor [LOWORD (wParam) - IDM_BKGND_WHITE])) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_APP_ABOUT: MessageBox (hwnd, TEXT ("Popup Menu Demonstration Program\n") TEXT ("(c) Charles Petzold, 1998"), szAppName, MB_ICONINFORMATION | MB_OK) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; case IDM_APP_HELP: MessageBox (hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } break ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } POPMENU.RC (摘錄)
//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu POPMENU MENU DISCARDABLE BEGIN POPUP "MyMenu" BEGIN POPUP "&File" BEGIN MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As", IDM_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR END POPUP "&Background" BEGIN MENUITEM "&White", IDM_BKGND_WHITE, CHECKED MENUITEM "&Light Gray", IDM_BKGND_LTGRAY MENUITEM "&Gray", IDM_BKGND_GRAY MENUITEM "&Dark Gray", IDM_BKGND_DKGRAY MENUITEM "&Black", IDM_BKGND_BLACK END POPUP "&Help" BEGIN MENUITEM "&Help...", IDM_APP_HELP MENUITEM "&About PopMenu...", IDM_APP_ABOUT END END END RESOURCE.H (摘錄)
// Microsoft Developer Studio generated include file. // Used by PopMenu.rc #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_SAVE 40003 #define IDM_FILE_SAVE_AS 40004 #define IDM_APP_EXIT 40005 #define IDM_EDIT_UNDO 40006 #define IDM_EDIT_CUT 40007 #define IDM_EDIT_COPY 40008 #define IDM_EDIT_PASTE 40009 #define IDM_EDIT_CLEAR 40010 #define IDM_BKGND_WHITE 40011 #define IDM_BKGND_LTGRAY 40012 #define IDM_BKGND_GRAY 40013 #define IDM_BKGND_DKGRAY 40014 #define IDM_BKGND_BLACK 40015 #define IDM_APP_HELP 40016 #define IDM_APP_ABOUT 40017 資源描述檔POPMENU.RC定義的菜單與MENUDEMO.RC中的菜單非常相似。不同的是,,在頂層菜單中只包含一項-一個彈出式菜單「MyMenu」,,它呼叫「File」、「Edit」,、「Background」和「Help」選項,。這四個選項垂直一行地出現(xiàn)在彈出式菜單上,而不是水平一列地出現(xiàn)在主菜單上,。 在WndProc中的WM_CREATE處理期間,,POPMENU取得此彈出式菜單的句柄,就是帶有文字「MyMenu」的那個彈出式菜單: hMenu = LoadMenu (hInst, szAppName) ; hMenu = GetSubMenu (hMenu, 0) ; 在WM_RBUTTONUP消息處理期間,,POPMENU提供了鼠標指針的位置,,將此位置轉(zhuǎn)換為屏幕坐標,再將坐標值傳遞給TrackPopupMenu: point.x = LOWORD (lParam) ; point.y = HIWORD (lParam) ; ClientToScreen (hwnd, &point) ; TrackPopupMenu (hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL) ; 然后,,Windows顯示出具有「File」,、「Edit」,、「Background」和「Help」項的彈出式菜單,。選擇其中任何一項都可以使嵌套的彈出式菜單顯示在右邊,菜單函數(shù)與一般的菜單一樣,。 如果要使用與該程序的主菜單相同的菜單并帶有TrackPopupMenu,,您會遇到一些問題,因為函數(shù)需要彈出式菜單句柄,。在「Microsoft Knowledge Base」文章ID Q99806有提供一些信息,。 使用系統(tǒng)菜單 使用WS_SYSMENU樣式建立的父窗口,在其標題列的左側(cè)有一個系統(tǒng)菜單按鈕,。如果您愿意,,可以修改這個菜單。在Windows程序設計的早期,程序?qū)懽髡咭话惆选窤bout」菜單項放入系統(tǒng)菜單,。雖然這種方法不常見,,但是修改系統(tǒng)菜單往往是一種在短程序中添加菜單的快速偷懶方法。這里唯一的限制是:在系統(tǒng)菜單中增加的命令其ID值必須小于0xF000,;否則它們將會與Windows系統(tǒng)菜單命令所使用的ID值相沖突,。還要記住,當您為這些新菜單項在窗口消息處理程序中處理WM_SYSCOMMAND消息時,,您必須把其它的WM_SYSCOMMAND消息發(fā)送給DefWindowProc,。如果您不這樣做,那么實際上是禁用了系統(tǒng)菜單上的所有正常選項,。 程序10-7中所示的POORMENU(「設計不當?shù)膫€人菜單」)在系統(tǒng)菜單中加入了一個分隔條和三個命令,,最后一個命令將刪除這些附加的菜單項。 程序10-7 POORMENU POORMENU.C /*------------------------------------------------------------------------- POORMENU.C -- The Poor Person's Menu (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #define IDM_SYS_ABOUT 1 #define IDM_SYS_HELP 2 #define IDM_SYS_REMOVE 3 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; static TCHAR szAppName[] = TEXT ("PoorMenu") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HMENU hMenu ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("The Poor-Person's Menu"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; hMenu = GetSystemMenu (hwnd, FALSE) ; AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_ABOUT, TEXT ("About...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_HELP, TEXT ("Help...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_REMOVE, TEXT ("Remove Additions")) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { switch (message) { case WM_SYSCOMMAND: switch (LOWORD (wParam)) { case IDM_SYS_ABOUT: MessageBox ( hwnd, TEXT ("A Poor-Person's Menu Program\n") TEXT ("(c) Charles Petzold, 1998"), szAppName, MB_OK | MB_ICONINFORMATION) ; return 0 ; case IDM_SYS_HELP: MessageBox ( hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; return 0 ; case IDM_SYS_REMOVE: GetSystemMenu (hwnd, TRUE) ; return 0 ; } break ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } 三個菜單ID在POORMENU.C的開始部分定義: #define IDM_ABOUT 1 #define IDM_HELP 2 #define IDM_REMOVE 3 在程序窗口建立之后,,POORMENU得到一個系統(tǒng)菜單的句柄: hMenu = GetSystemMenu (hwnd, FALSE) ; 第一次呼叫GetSystemMenu時,,您應該為修改菜單作準備,將第二個參數(shù)設定為FALSE,。 使用四個AppendMenu呼叫來實作對菜單的修改: AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_ABOUT, TEXT ("About...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_HELP, TEXT ("Help...")) ; AppendMenu (hMenu, MF_STRING, IDM_SYS_REMOVE, TEXT ("Remove Additions")); 第一個AppendMenu呼叫是添加分隔條,。選擇「Remove Additions」菜單項將使POORMENU刪除這些附加的菜單項,這只要把第二個參數(shù)設定為TRUE,,再次呼叫GetSystemMenu即可: GetSystemMenu (hwnd, TRUE) ; 標準系統(tǒng)菜單有下列選項:Restore,、Move、Size,、Minimize,、Maximize和Close。它們產(chǎn)生wParam分別等于SC_RESTORE,、SC_MOVE,、SC_SIZE、SC_MINIMUM,、SC_MAXIMUM和SC_CLOSE的WM_SYSCOMMAND消息,。盡管Windows程序一般不這樣做,但是您可以自己處理這些消息,,而不把它們留給DefWindowProc,。您也可以使用下面所述的方法來禁止或者除掉系統(tǒng)菜單的標準選項。Windows文件中還介紹了一些系統(tǒng)菜單的標準附加項目,,這些附加項目使用標識符SC_NEXTWINDOW、SC_PREVWINDOW,、SC_VSCROLL,、SC_HSCROLL和SC_ARRANGE,。您也許會發(fā)現(xiàn),在一些應用程序中將這些命令加入系統(tǒng)菜單是合適的,。 改變菜單 我們已經(jīng)看到了如何使用AppendMenu函數(shù)為程序定義菜單以及將菜單項加入到系統(tǒng)菜單中,。在Windows 3.0之前,您不得不被迫使用ChangeMenu函數(shù)來完成這種工作,。ChangeMenu函數(shù)有很多功能,,至少在當時,整個Windows中它是最復雜的函數(shù)之一?,F(xiàn)在,,許多函數(shù)都比ChangeMenu函數(shù)還要復雜,并且ChangeMenu的功能被分解為五個新的函數(shù):
如果菜單項是一個彈出式菜單,,那么DeleteMenu和RemoveMenu之間的區(qū)別就很重要,。DeleteMenu清除彈出式菜單,,但RemoveMenu不清除它,。 其它菜單命令 下面是在使用菜單時一些有用的函數(shù),。 當您改變頂層菜單項時,直到Windows重畫菜單列時才顯示所做的改變,。您可以通過下列呼叫來強迫執(zhí)行菜單更新: DrawMenuBar (hwnd) ; 注意,,DrawMenuBar的參數(shù)是窗口句柄而不是菜單句柄。 您可以使用下列命令來獲得彈出式菜單的句柄: hMenuPopup = GetSubMenu (hMenu, iPosition) ; 其中iPosition是hMenu指示的頂層菜單中彈出式菜單項的索引(開始為0),。然后您可以在其它函數(shù)中使用彈出式菜單句柄(例如在AppendMenu函數(shù)中),。 您可以使用下列命令獲得頂層菜單或者彈出式菜單中目前的項數(shù): iCount = GetMenuItemCount (hMenu) ; 您可以取得彈出式菜單項的菜單ID: id = GetMenuItemID (hMenuPopup, iPosition) ; 其中iPosition是菜單項在彈出式菜單中的位置(以0開始)。 在MENUDEMO中您已經(jīng)看到如何選中,、或者取消選中彈出式菜單中的某一項: CheckMenuItem (hMenu, id, iCheck) ; 在MENUDEMO中,,hMenu是頂層菜單的句柄,id是菜單ID,,而iCheck的值是MF_CHECKED或MF_UNCHECKED,。如果hMenu是彈出式菜單句柄,那么參數(shù)id是位置索引而不是菜單ID,。如果使用索引會更方便的話,,那么您可以在第三個參數(shù)中包含MF_BYPOSITION,例如: CheckMenuItem (hMenu, iPosition, MF_CHECKED | MF_BYPOSITION) ; 除了第三個參數(shù)是MF_ENABLED,、MF_DISABLED或MF_GRAYED外,,EnableMenuItem函數(shù)與CheckMenuItem函數(shù)所完成的工作類似。如果您在具有彈出式菜單的頂層菜單項上使用EnableMenuItem,,那么必須在第三個參數(shù)中使用MF_BYPOSITION標識符,因為菜單項沒有菜單ID,。我們將在本章后面所示的POPPAD2程序中看到EnableMenuItem的一個例子,。 HiliteMenuItem也類似于CheckMenuItem和EnableMenuItem,,但是它使用的是MF_HILITE和MF_UNHILITE。當您在菜單項之間移動時,,Windows使用反白顯示方式加亮顯示菜單項,。您通常不需要使用HiliteMenuItem。 您還需要對您的菜單做些什么呢,?還記得我們在菜單中使用了哪些字符串嗎,?您可以透過下面的呼叫來回顧一下: iCharCount = GetMenuString (hMenu, id, pString, iMaxCount, iFlag) ; iFlag可以是MF_BYCOMMAND(其中id是菜單ID),也可以是MF_BYPOSITION(其中的id是位置索引),。函數(shù)將字符串的iMaxCount個字節(jié)復制到pString中,,并傳回復制的字節(jié)數(shù)。 或許您也想知道菜單項目前的屬性是什么: iFlags = GetMenuState (hMenu, id, iFlag) ; 同樣地,,iFlag可以是MF_BYCOMMAND或MF_BYPOSITION,。傳回值iFlags是目前所有屬性的組合,您可以通過對MF_DISABLED,、MF_GRAYED,、MF_CHECKED、MF_MENUBREAK,、MF_MENUBARBREAK和MF_SEPARATOR標識符的檢測來決定目前的屬性,。 也許現(xiàn)在您對菜單有了一些了解。這時您可能想知道,,如果您不再需要菜單時又應該如何處理,。您可以使用下面的命令來清除菜單: DestroyMenu (hMenu) ; 從而使菜單句柄無效。 建立菜單的非正統(tǒng)方法 現(xiàn)在讓我們稍微偏離我們所討論的主題,。如果在您的程序中沒有下拉式菜單,,而是建立了多個沒有彈出式菜單的頂層菜單,并呼叫SetMenu在頂層菜單之間切換,,那會是什么樣的情形呢,?就像Lotus 1-2-3中老式的文字模式菜單那樣。程序10-8中的NOPOPUPS程序展示了處理這種情況,。在這個程序中,,「File」和「Edit」項與MENUDEMO程序中的類似,但是卻以另一種頂層菜單顯示出來,。 程序10-8 NOPOPUPS
NOPOPUPS.C /*------------------------------------------------------------------------- NOPOPUPS.C -- Demonstrates No-Popup Nested Menu (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("NoPopUps") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("No-Popup Nested Menu Demonstration"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HMENU hMenuMain, hMenuEdit, hMenuFile ; HINSTANCE hInstance ; switch (message) { case WM_CREATE: hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) ; hMenuMain = LoadMenu (hInstance, TEXT ("MenuMain")) ; hMenuFile = LoadMenu (hInstance, TEXT ("MenuFile")) ; hMenuEdit = LoadMenu (hInstance, TEXT ("MenuEdit")) ; SetMenu (hwnd, hMenuMain) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_MAIN: SetMenu (hwnd, hMenuMain) ; return 0 ; case IDM_FILE: SetMenu (hwnd, hMenuFile) ; return 0 ; case IDM_EDIT: SetMenu (hwnd, hMenuEdit) ; return 0 ; case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: case IDM_EDIT_UNDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: MessageBeep (0) ; return 0 ; } break ; case WM_DESTROY: SetMenu (hwnd, hMenuMain) ; DestroyMenu (hMenuFile) ; DestroyMenu (hMenuEdit) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } NOPOPUPS.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu MENUMAIN MENU DISCARDABLE BEGIN MENUITEM "MAIN:", 0, INACTIVE MENUITEM "&File...", IDM_FILE MENUITEM "&Edit...", IDM_EDIT END MENUFILE MENU DISCARDABLE BEGIN MENUITEM "FILE:", 0, INACTIVE MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open...", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As", IDM_FILE_SAVE_AS MENUITEM "(&Main)", IDM_MAIN END MENUEDIT MENU DISCARDABLE BEGIN MENUITEM "EDIT:", 0, INACTIVE MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM "Cu&t", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR MENUITEM "(&Main)", IDM_MAIN END RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by NoPopups.rc #define IDM_FILE 40001 #define IDM_EDIT 40002 #define IDM_FILE_NEW 40003 #define IDM_FILE_OPEN 40004 #define IDM_FILE_SAVE 40005 #define IDM_FILE_SAVE_AS 40006 #define IDM_MAIN 40007 #define IDM_EDIT_UNDO 40008 #define IDM_EDIT_CUT 40009 #define IDM_EDIT_COPY 40010 #define IDM_EDIT_PASTE 40011 #define IDM_EDIT_CLEAR 40012 在Microsoft Developer Studio中,,您建立了三個菜單,而不是一個,。從「Insert」中選擇「Resource」三次,,每個菜單有一個不同的名稱。當窗口消息處理程序處理WM_CREATE消息時,,Windows將每個菜單資源加載內(nèi)存: hMenuMain = LoadMenu (hInstance, TEXT ("MenuMain")) ; hMenuFile = LoadMenu (hInstance, TEXT ("MenuFile")) ; hMenuEdit = LoadMenu (hInstance, TEXT ("MenuEdit")) ; 開始時,,程序只顯示主菜單: SetMenu (hwnd, hMenuMain) ; 主菜單使用字符串「MAIN:」,、「File...」和「Edit...」列出這三個選項。然而,,「MAIN:」是禁用的,,因此它不能使WM_COMMAND消息被發(fā)送到窗口消息處理程序?!窮ile」和「Edit」菜單項以「FILE:」和「EDIT:」開始,,表示它們是子菜單。每個菜單的最后一項都是字符串「(Main)」,,表示傳回到主菜單,。在這三個菜單之間進行切換是很簡單的: case WM_COMMAND : switch (wParam) { case IDM_MAIN : SetMenu (hwnd, hMenuMain) ; return 0 ; case IDM_FILE : SetMenu (hwnd, hMenuFile) ; return 0 ; case IDM_EDIT : SetMenu (hwnd, hMenuEdit) ; return 0 ; 其它行程序 } break ; 在WM_DESTROY消息處理期間,NOPOPUPS將程序的菜單設定為主菜單,,并呼叫DestroyMenu來清除「File」和「Edit」菜單,。當窗口被清除時,主菜單將被自動清除,。 快捷鍵是產(chǎn)生WM_COMMAND消息(有些情況下是WM_SYSCOMMAND)的鍵組合,。許多時候,程序使用快捷鍵來重復常用菜單項的動作(然而,,快捷鍵還可以用于執(zhí)行非菜單功能),。例如,許多Windows程序都有一個包含「Delete」或「Clear」選項的「Edit」菜單,,這些程序習慣上都將Del鍵指定為該選項的快捷鍵,。使用者可以通過「 Alt鍵」從菜單中選擇「Delete」選項,或者只需按下快捷鍵 Del,。當窗口消息處理程序收到一個WM_COMMAND消息時,,它不必確定使用的是菜單還是快捷鍵。 為什么要使用快捷鍵 您也許會問:為什么我應該使用快捷鍵?為什么不能直接攔截WM_KEYDOWN或WM_CHAR消息而自己實作同樣的菜單功能呢,?好處又在哪里呢,?對于一個單窗口應用程序,您當然可以攔截鍵盤消息,,但是使用快捷鍵可以得到一些好處:您不需要把菜單和快捷鍵的處理方式重寫一遍,。 對于有多個窗口和多個窗口消息處理程序的應用程序來說,快捷鍵是非常重要的,。正如我們所看到的,,Windows將鍵盤消息發(fā)送給目前活動窗口的窗口消息處理程序。然而對于快捷鍵,,Windows把WM_COMMAND消息發(fā)送給窗口消息處理程序,,該窗口消息處理程序的句柄在Windows函數(shù)TranslateAccelerator中給出。通常這是主窗口,,也是擁有菜單的窗口,,這意味著無須每個窗口消息處理程序都把快捷鍵的操作處理程序重寫一遍,。 如果您在主窗口的顯示區(qū)域中,,使用了非系統(tǒng)模態(tài)對話框(在下一章中會討論)或者子窗口,,那么這種好處就變得非常重要。如果定義一個特定的快捷鍵以便在不同的窗口之間移動,,那么,,只需要一個窗口消息處理程序有這個處理程序。子窗口就不會收到快捷鍵引發(fā)的WM_COMMAND消息,。 安排快捷鍵的幾條規(guī)則 理論上,,您可以使用任何虛擬鍵或者字符鍵連同Shift鍵、Ctrl鍵或Alt鍵來定義快捷鍵,。然而,,您應該盡力使應用程序之間協(xié)調(diào)一致,并且盡量避免干擾Windows的鍵盤使用,。在快捷鍵中,,應該避免使用Tab、Enter,、Esc和Spacebar鍵,,因為這些鍵常常用于完成系統(tǒng)功能。 快捷鍵最經(jīng)常的用途是操作程序的「Edit」菜單中的各項,。為這些菜單項推薦的快捷鍵在Windows 3.0和Windows 3.1之間已有不同,,因此通常都要支持如下所列的新舊兩套快捷鍵:
另一種常用的虛擬鍵是啟動輔助信息的功能鍵F1。應該避免使用F4,、F5和F6鍵,,因為這些鍵常用在多重文件接口(MDI)程序中來完成特殊的功能(將在第十九章中討論)。 快捷鍵表 您可以在Developer Studio中定義快捷鍵表,。為了讓程序中加載加速鍵表更為容易,,給它和程序名相同的名稱(與菜單和圖示名也相同)。 每個快捷鍵都有在Accel Properties對話框中定義的ID和按鍵組合,。如果您已經(jīng)定義了菜單,,則菜單ID會出現(xiàn)在下拉式清單方塊中,因此不需要鍵入它們,。 快捷鍵可以是虛擬鍵或ASCII字符與Shift,、Ctrl或Alt鍵的組合??梢酝ㄟ^在字母前鍵入『^』來指定帶有Ctrl鍵的ASCII字符,。也可以從下拉式清單方塊中選取虛擬鍵。 當您為菜單項定義快捷鍵時,,應該將鍵的組合包含到菜單項的文字中,。制表符(\t)將文字與快捷鍵分割開,,將快捷鍵列在第二列。為了在菜單中為快捷鍵做上標記,,可以在文字「Ctrl」,、「Shift」或「Alt」之后跟上一個「+」號和一個鍵名(例如,「Shift+F6」或「Ctrl+F6」),。 快捷鍵表的加載 在您的程序中,,您使用LoadAccelerators函數(shù)把快捷鍵表加載內(nèi)存,并獲得該表的句柄,。 LoadAccelerators敘述非常類似于LoadIcon,、LoadCursor和LoadMenu敘述。 首先,,把快捷鍵表的句柄定義為型態(tài)HANDLE: HANDLE hAccel ; 然后加載加速鍵表: hAccel = LoadAccelerators (hInstance, TEXT ("MyAccelerators")) ; 正如圖標,、光標和菜單一樣,您可以使用一個數(shù)值代替快捷鍵表的名稱,,然后在LoadAccelerators敘述中和MAKEINTRESOURCE宏一起使用該數(shù)值,,或者把它放在雙引號內(nèi),前面冠以字符「#」,。 鍵盤代碼轉(zhuǎn)換 現(xiàn)在我們將討論底下這三行程序代碼,,在本書中,截至目前為止建立的所有Windows程序中都使用過它們,。這些程序代碼是標準的消息循環(huán): while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } 下面把上頭那段程序代碼加以修改,,以便使用加速鍵: while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } TranslateAccelerator函數(shù)確認存放在msg消息結(jié)構(gòu)中的消息是否為鍵盤消息。如果是,,該函數(shù)將找尋句柄為hAccel的快捷鍵表,。如果找到了一個符合的,則呼叫句柄為hwnd的窗口消息處理程序,。如果快捷鍵ID與系統(tǒng)菜單的菜單項一致,,則消息就是WM_SYSCOMMAND;否則,,消息為WM_COMMAND,。 當TranslateAccelerator傳回時,如果消息已經(jīng)被轉(zhuǎn)換(并且已經(jīng)被發(fā)送給窗口消息處理程序),,那么傳回值為非零,;否則,傳回值為0,。如果TranslateAccelerator傳回一個非零值,,則不呼叫TranslateMessage和DispatchMessage,而是經(jīng)過循環(huán)回到GetMessage呼叫中。 TranslateMessage中的參數(shù)hwnd看起來有點累贅,,因為消息循環(huán)中的其它三個函數(shù)都沒有要求這個參數(shù)。此外,,消息結(jié)構(gòu)本身(結(jié)構(gòu)變量msg)有一個叫做hwnd的成員,它是窗口句柄,。 該函數(shù)有些不同的原因在于:msg結(jié)構(gòu)的字段由GetMessage呼叫填入,。當GetMessage的第二個參數(shù)為NULL時,,函數(shù)會找尋應用程序所有窗口的消息,。當GetMessage傳回時,msg結(jié)構(gòu)的hwnd是將要獲得消息之窗口的窗口句柄,。然而,,當TranslateAccelerator把鍵盤消息轉(zhuǎn)換為WM_COMMAND或WM_SYSCOMMAND消息時,它使用函數(shù)的第一個參數(shù)指定的窗口句柄hwnd來代替窗口代號msg.hwnd,。Windows就是這樣把所有快捷鍵消息發(fā)送給同一窗口消息處理程序的,,即使另一個應用窗口目前擁有輸入焦點。當系統(tǒng)模態(tài)對話框或者消息框擁有輸入焦點時,,TranslateAccelerator不會轉(zhuǎn)換鍵盤消息,,因為這些窗口的消息是不經(jīng)過程序的消息循環(huán)的。 在某些情況下,,當您程序的另一個窗口(比如一個非系統(tǒng)模態(tài)對話框)擁有輸入焦點時,,您也許不想轉(zhuǎn)換快捷鍵。您將在 下一章中看到如何處理這種情況,。 接收快捷鍵消息 當快捷鍵與系統(tǒng)菜單中的菜單項相對應時,,TranslateAccelerator給窗口消息處理程序發(fā)送一個WM_SYSCOMMAND消息,否則,,TranslateAccelerator給窗口消息處理程序發(fā)送一個WM_COMMAND消息,。下表所示為幾種可能接收到的WM_COMMAND消息,這些消息用于快捷鍵,、菜單命令以及子窗口控件:
如果快捷鍵與一個菜單項對應,,那么窗口消息處理程序還會收到WM_INITMENU、WM_INITMENUPOPUP和WM_MENUSELECT消息,,就好像選中了菜單選項一樣,。在處理WM_INITMENUPOPUP時,程序往往啟用和禁用彈出式菜單中的菜單項,,因此,,在使用快捷鍵時,您仍然能夠?qū)嵶鬟@類功能。如果快捷鍵與一個禁用或者無效化的菜單項相對應,,那么,,TranslateAccelerator函數(shù)就不會向窗口消息處理程序發(fā)送WM_COMMAND或WM_SYSCOMMAND消息。 如果活動窗口已經(jīng)被最小化,,那么TranslateAccelerator將為與啟用的系統(tǒng)菜單項相對應的快捷鍵向窗口消息處理程序發(fā)送WM_SYSCOMMAND消息,,而不是WM_COMMAND消息。TranslateAccelerator也會為沒有任何菜單項與之對應的快捷鍵,,來向窗口消息處理程序發(fā)送WM_COMMAND消息,。 菜單與快捷鍵應用程序POPPAD 在第九章,我們建立了一個叫做POPPAD1的程序,,它使用了子窗口編輯控件來實作基本的筆記本功能,。在這一章中,我們將加入「File」和「Edit」菜單,,并稱此程序為POPPAD2,。「Edit」菜單的菜單項的功能全部可用,;我們將在第十一章中完成「File」功能,,在 第十三章中完成「Print」功能。POPPAD2如程序10-9所示,。 程序10-9 POPPAD2
POPPAD2.C /*--------------------------------------------------------------------------- POPPAD2.C -- Popup Editor Version 2 (includes menu) (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" #define ID_EDIT 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); TCHAR szAppName[] = TEXT ("PopPad2") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HACCEL hAccel ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (hInstance, szAppName) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, szAppName, WS_OVERLAPPEDWINDOW, GetSystemMetrics (SM_CXSCREEN) / 4, GetSystemMetrics (SM_CYSCREEN) / 4, GetSystemMetrics (SM_CXSCREEN) / 2, GetSystemMetrics (SM_CYSCREEN) / 2, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hAccel = LoadAccelerators (hInstance, szAppName) ; while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } return msg.wParam ; } AskConfirmation (HWND hwnd) { return MessageBox ( hwnd, TEXT ("Really want to close PopPad2?"), szAppName, MB_YESNO | MB_ICONQUESTION) ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HWND hwndEdit ; int iSelect, iEnable ; switch (message) { case WM_CREATE: hwndEdit = CreateWindow (TEXT ("edit"), NULL, WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | WS_BORDER | ES_LEFT | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, ((LPCREATESTRUCT) lParam)->hInstance, NULL) ; return 0 ; case WM_SETFOCUS: SetFocus (hwndEdit) ; return 0 ; case WM_SIZE: MoveWindow (hwndEdit, 0, 0, LOWORD (lParam), HIWORD (lParam), TRUE) ; return 0 ; case WM_INITMENUPOPUP: if (lParam == 1) { EnableMenuItem ((HMENU) wParam, IDM_EDIT_UNDO, SendMessage (hwndEdit, EM_CANUNDO, 0, 0) ? MF_ENABLED : MF_GRAYED) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE, IsClipboardFormatAvailable (CF_TEXT) ? MF_ENABLED : MF_GRAYED) ; iSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0) ; if (HIWORD (iSelect) == LOWORD (iSelect)) iEnable = MF_GRAYED ; else iEnable = MF_ENABLED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CLEAR, iEnable) ; return 0 ; } break ; case WM_COMMAND: if (lParam) { if (LOWORD (lParam) == ID_EDIT && (HIWORD (wParam) == EN_ERRSPACE || HIWORD (wParam) == EN_MAXTEXT)) MessageBox (hwnd, TEXT ("Edit control out of space."), szAppName, MB_OK | MB_ICONSTOP) ; return 0 ; } else switch (LOWORD (wParam)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: case IDM_FILE_PRINT: MessageBeep (0) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; case IDM_EDIT_UNDO: SendMessage (hwndEdit, WM_UNDO, 0, 0) ; return 0 ; case IDM_EDIT_CUT: SendMessage (hwndEdit, WM_CUT, 0, 0) ; return 0 ; case IDM_EDIT_COPY: SendMessage (hwndEdit, WM_COPY, 0, 0) ; return 0 ; case IDM_EDIT_PASTE: SendMessage (hwndEdit, WM_PASTE, 0, 0) ; return 0 ; case IDM_EDIT_CLEAR: SendMessage (hwndEdit, WM_CLEAR, 0, 0) ; return 0 ; case IDM_EDIT_SELECT_ALL: SendMessage (hwndEdit, EM_SETSEL, 0, -1) ; return 0 ; case IDM_HELP_HELP: MessageBox (hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; return 0 ; case IDM_APP_ABOUT: MessageBox (hwnd, TEXT ("POPPAD2 (c) Charles Petzold, 1998"), szAppName, MB_OK | MB_ICONINFORMATION) ; return 0 ; } break ; case WM_CLOSE: if (IDYES == AskConfirmation (hwnd)) DestroyWindow (hwnd) ; return 0 ; case WM_QUERYENDSESSION: if (IDYES == AskConfirmation (hwnd)) return 1 ; else return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; } POPPAD2.RC (摘錄)
//Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Icon POPPAD2 ICON DISCARDABLE "poppad2.ico" ///////////////////////////////////////////////////////////////////////////// // Menu POPPAD2 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open...", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "&Print", IDM_FILE_PRINT MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo\tCtrl+Z", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE MENUITEM "De&lete\tDel", IDM_EDIT_CLEAR MENUITEM SEPARATOR MENUITEM "&Select All", IDM_EDIT_SELECT_ALL END POPUP "&Help" BEGIN MENUITEM "&Help...", IDM_HELP_HELP MENUITEM "&About PopPad2...", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Accelerator POPPAD2 ACCELERATORS DISCARDABLE BEGIN VK_BACK, IDM_EDIT_UNDO, VIRTKEY, ALT, NOINVERT VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT VK_DELETE, IDM_EDIT_CUT, VIRTKEY, SHIFT, NOINVERT VK_F1, IDM_HELP_HELP, VIRTKEY, NOINVERT VK_INSERT, IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT VK_INSERT, IDM_EDIT_PASTE, VIRTKEY, SHIFT, NOINVERT "^C", IDM_EDIT_COPY, ASCII, NOINVERT "^V", IDM_EDIT_PASTE, ASCII, NOINVERT "^X", IDM_EDIT_CUT, ASCII, NOINVERT "^Z", IDM_EDIT_UNDO, ASCII, NOINVERT END RESOURCE.H (摘錄)
// Microsoft Developer Studio generated include file. // Used by POPPAD2.RC #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_SAVE 40003 #define IDM_FILE_SAVE_AS 40004 #define IDM_FILE_PRINT 40005 #define IDM_APP_EXIT 40006 #define IDM_EDIT_UNDO 40007 #define IDM_EDIT_CUT 40008 #define IDM_EDIT_COPY 40009 #define IDM_EDIT_PASTE 40010 #define IDM_EDIT_CLEAR 40011 #define IDM_EDIT_SELECT_ALL 40012 #define IDM_HELP_HELP 40013 #define IDM_APP_ABOUT 40014
POPPAD2.RC資源描述文件包含菜單和快捷鍵。您將注意到,,所有快捷鍵都表示在制表符(\t)后的「Edit」彈出式菜單的字符串中,。 啟用菜單項 窗口消息處理程序的工作包括啟用和無效化「Edit」菜單中的選項,這項工作在處理WM_INITMENUPOPUP時完成,。首先,程序檢查是否要顯示「Edit」彈出式菜單,。因為菜單里「Edit」的位置索引(「File」從0開始)是1,,因此如果即將顯示「Edit」彈出式菜單,那么lParam應該等于1,。 為了確定是否啟用「Undo」選項,,POPPAD2給編輯控件發(fā)送一條EM_CANUNDO消息。如果編輯控件能夠執(zhí)行「Undo」動作,,那么SendMessage呼叫傳回非零值,。在這種情況下,選項被啟用;否則,,選項無效化: EnableMenuItem (wParam, IDM_UNDO, SendMessage (hwndEdit, EM_CANUNDO, 0, 0) ? MF_ENABLED : MF_GRAYED) ; 只有當剪貼簿中包含文字時,,「Paste」選項才能夠被啟用。我們可以使用CF_TEXT標識符通過IsClipboardFormatAvailable呼叫來確定這一點: EnableMenuItem (wParam, IDM_PASTE, IsClipboardFormatAvailable (CF_TEXT) ? MF_ENABLED : MF_GRAYED) ; 只有選擇了編輯控件中的文字,,「Cut」,、「Copy」和「Delete」選項才能夠被啟用。給編輯控件發(fā)送一條EM_GETSEL消息,,并傳回包含此信息的整數(shù): iSelect = SendMessage (hwndEdit, EM_GETSEL, 0, 0) ; iSelect的低位字是第一個被選中字符的位置,,iSelect的高字組是下一個被選中字符的位置。如果這兩個字相等,,則表示沒有選中文字: if (HIWORD (iSelect) == LOWORD (iSelect)) iEnable = MF_GRAYED ; else iEnable = MF_ENABLED ; 然后可以將iEnable的值用于「Cut」,、「Copy」和「Delete」選項: EnableMenuItem (wParam, IDM_CUT, iEnable) ; EnableMenuItem (wParam, IDM_COPY, iEnable) ; EnableMenuItem (wParam, IDM_DEL, iEnable) ; 處理菜單項 當然,如果POPPAD2程序不使用子窗口編輯控件,,那么我們將面臨一些問題,,這涉及如何完成「Edit」菜單中的「Undo」、「Cut」,、「Copy」、「Paste」,、「Clear」和「Select All」選項,。正是編輯控件使得這種處理變得容易,因為對于每一個選項我們只需向編輯控件發(fā)送一個消息即可: case IDM_UNDO : SendMessage (hwndEdit, WM_UNDO, 0, 0) ; return 0 ; case IDM_CUT : SendMessage (hwndEdit, WM_CUT, 0, 0) ; return 0 ; case IDM_COPY : SendMessage (hwndEdit, WM_COPY, 0, 0) ; return 0 ; case IDM_PASTE : SendMessage (hwndEdit, WM_PASTE, 0, 0) ; return 0 ; case IDM_DEL : SendMessage (hwndEdit, WM_DEL, 0, 0) ; return 0 ; case IDM_SELALL : SendMessage (hwndEdit, EM_SETSEL, 0, -1) ; return 0 ; 注意,,我們可以更進一步簡化這些處理-只要使IDM_UNDO,、IDM_CUT等等的值等于相對應的窗口消息WM_UNDO、WM_CUT的值,。 「File」彈出式菜單上的「About」選項啟動一個簡單的消息框: case IDM_ABOUT : MessageBox (hwnd, TEXT ("POPPAD2 (c) Charles Petzold, 1998"), szAppName, MB_OK | MB_ICONINFORMATION) ; return 0 ; 在下一章中,,我們將把它變成一個對話框,。當您從菜單中選擇「Help」選項或者按下F1快捷鍵時,,同樣可以啟動一個消息框。 「Exit」選項向窗口消息處理程序發(fā)送一個WM_CLOSE消息: case IDM_EXIT : SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; 這正是DefWindowProc收到一個wParam等于SC_CLOSE的WM_SYSCOMMAND消息時所完成的工作,。 在前面的那些程序中,我們沒有在窗口消息處理程序中處理WM_CLOSE消息,,而只是簡單地把它送給DefWindowProc,。DefWindowProc對WM_CLOSE的處理非常簡單:呼叫DestroyWindow函數(shù)??梢圆话裌M_CLOSE消息送給DefWindowProc,,而讓POPPAD2來處理它。這個事實到目前為止并不重要,但是在第十一章中當POPPAD可以真正編輯文字時,,它就變得非常重要了,。 case WM_CLOSE : if (IDYES == AskConfirmation (hwnd)) DestroyWindow (hwnd) ; return 0 ; AskConfirmation是POPPAD2中的一個函數(shù),,它顯示一個請求確認關(guān)閉程序的消息框: AskConfirmation (HWND hwnd) { return MessageBox (hwnd, TEXT ("Really want to close Poppad2?"), szAppName, MB_YESNO | MB_ICONQUESTION) ; } 如果選擇了Yes按鈕的話,消息框(以及AskConfirmation函數(shù))將傳回IDYES,。只有這樣,,程序才會呼叫DestroyWindow,否則,,程序不會結(jié)束,。 如果要在程序結(jié)束之前確認使用者真的要結(jié)束程序,那么您還必須處理WM_QUERYENDSESSION消息,。當使用者要關(guān)閉Windows時,,Windows開始向每個窗口消息處理程序發(fā)送一個WM_QUERYENDSESSION消息。如果有任何一個窗口消息處理程序處理這個消息后傳回0,,那么Windows將不會結(jié)束,。我們?nèi)缦绿幚砹薟M_QUERYENDSESSION: case WM_QUERYENDSESSION : if (IDYES == AskConfirmation (hwnd)) return 1 ; else return 0 ; 如果要在程序結(jié)束之前要求使用者的確認,必須處理WM_CLOSE和WM_QUERYENDSESSION這兩個消息,,這就是為什么我們使POPPAD2中的「Exit」菜單選項只向窗口消息處理程序發(fā)送一個WM_CLOSE消息的原因,。這樣做,我們避免了在別處進行請求確認的動作,。 如果要處理WM_QUERYENDSESSION消息,,那么您也許還會對WM_ENDSESSION消息感興趣。Windows把這個消息發(fā)送給先前收到WM_QUERYENDSESSION消息的每個窗口消息處理程序,。如果由于另一個程序從WM_QUERYENDSESSION傳回了0而不能結(jié)束Windows的執(zhí)行,,那么WM_ENDSESSION的wParam參數(shù)為0。WM_ENDSESSION消息實際上回答了這個問題:我告訴過Windows可以把我結(jié)束掉,,但是我真的被結(jié)束掉了嗎,? 盡管在POPPAD2的「File」菜單中我加上了常見的「New」、「Open」,、「Save」和「Save As」選項,,但是它們現(xiàn)在并不起作用。要處理這些命令,,我們需要使用對話框?,F(xiàn)在是討論對話框的時機,也是您準備學習它們的時候了,。 |
|