二、Windows菜单—菜单和消息
当使用者选择一个菜单项时,Windows通常向窗口消息处理程序发送几个不同的消息。在大多数情况下, 您的程序可以忽略大部分消息,只需把它们传递给DefWindowProc即可。WM_INITMENU就是这一类的消息,它具有下列参数:
wParam: 主菜单句柄
lParam: 0
wParam值是您的主菜单句柄,即使使用者选择的是系统菜单中的项目。Windows程序通常忽略WM_INITMENU消息。尽管在选中该项之前的消息已经给程序提供了修改菜单的机会,但是我们觉得此刻改变顶层菜单是会扰乱使用者的。
程序也会接收到WM_MENUSELECT消息。随着使用者在菜单项中移动光标或者鼠标,程序会收到许多WM_MENUSELECT消息。这对实作那些包含对菜单项的文字描述的状态列是很有帮助的。WM_MENUSELECT的参数如下所示:
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。如果您需要根据对菜单项的选择来改变窗口显示区域的内容,那么您可以使用WM_MENUSELECT消息。许多程序把该消息发送给DefWindowProc。
当Windows准备显示一个弹出式菜单时,它给窗口消息处理程序发送一个WM_INITMENUPOPUP消息,参数如下:
wParam: 弹出式菜单句柄
LOWORD (lParam):弹出式菜单索引
HIWORD (lParam): 系统菜单为1,其它为0
如果您需要在显示弹出式菜单之前启用或者禁用菜单项,那么这个消息就很重要。例如,假定程序使用弹出式菜单上的 Paste命令从剪贴簿复制文字,当您收到弹出式菜单中的WM_INITMENUPOPUP消息时,应确定剪贴簿内是否有文字存在。如果没有,那么应该使 Paste菜单项无效化。我们将在本章后面修改的POPPAD程序中看到这样的例子。
最重要的菜单消息是WM_COMMAND,它表示使用者已经从菜单中选中了一个被启用的菜单项。第八章中的WM_COMMAND消息也可以由子窗口控件产生。如果您碰巧为菜单和子窗口控件使用同一ID码,那么您可以通过lParam的值来区别它们,菜单项的lParam其值为0,请参见表10-1。
表10-1 |
菜单 |
控件 |
|
LOWORD (wParam): |
菜单ID |
控件ID |
HIWORD (wParam): |
0 |
通知码 |
lParam: |
0 |
子窗口句柄 |
WM_SYSCOMMAND消息类似于WM_COMMAND消息,只是WM_SYSCOMMAND表示使用者从系统菜单中选择一个启用的菜单项:
wParam: 菜单ID
lParam: 0
然而,如果WM_SYSCOMMAND消息是由按鼠标按键产生的,LOWORD(lParam)和HIWORD(lParam)将包含鼠标光标位置的x和y屏幕坐标。
对于WM_SYSCOMMAND,菜单ID指示系统菜单中的哪一项被选中。对于预先定义的系统菜单项,较低的那四个位应该和0xFFF0进行AND运算来屏蔽掉,结果值应该为下列之一: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。
如果您在系统菜单中添加菜单项,那么wParam的低字组将是您定义的菜单ID。为了避免与预先定义的菜单ID相冲突,应用程序应该使用小于0xF000的值,这对于将一般的WM_SYSCOMMAND消息发送给DefWindowProc是很重要的。如果您不这样做,那么您实际上就是禁用了正常的系统菜单命令。
我们将讨论的最后一个消息是WM_MENUCHAR。实际上,它根本不是菜单消息。在下列两种情况之一发生时,Windows会把这个消息发送到窗口消息处理程序:如果使用者按下Alt和一个与菜单项不匹配的字符时,或者在显示弹出式菜单而使用者按下一个与弹出式菜单里的项目不匹配的字符键时。随WM_MENUCHAR消息一起发送的参数如下所示:
LOWORD (wParam): 字符代码(ASCII或Unicode)
HIWORD (wParam): 选择码
lParam: 菜单句柄
选择码是:
-
0 不显示弹出式菜单
-
MF_POPUP 显示弹出式菜单
-
MF_SYSMENU 显示系统弹出式菜单
Windows程序通常把该消息传递给DefWindowProc,它一般给Windows传回0,这会使Windows发出哔声。在 第十四章GRAFMENU程序中会看到WM_MENUCHAR消息的使用。
范例程序
让我们来看一个简单的例子。程序10-4所示的MENUDEMO程序,在主菜单中有五个选择项-File、Edit、Background、Timer和Help,每一项都与一个弹出式菜单相连。MENUDEMO只完成了最简单、最通用的菜单处理操作,包括拦截WM_COMMAND消息和检查wParam的低字组。
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」。大多数项目有底线字母,这就是说您必须在字母前键入『&』。MENUITEM SEPARATOR叙述是在「Menu Item Properties」对话框中选中「Separator」框产生的。注意菜单中有一个项目具有「 Checked」选项,另一个具有「Grayed」选项。还有,「 Background」弹出式菜单中的五个项目应该按顺序输入,确保标识符是以数值的顺序,本程序需要这样。所有菜单项的标识符定义在RESOURCE.H中。
当收到弹出式菜单「File」和「Edit」各项有关的WM_COMMAND消息时,MENUDEMO程序只使系统发出哔声。「 Background」弹出式菜单列出MENUDEMO用来给背景着色的五种现有画刷。在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函数用来取消目前被选中的项目:
CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ;
iSelection的值被设定为wParam的值,新的背景颜色被选中:
iSelection = wParam ; CheckMenuItem (hMenu, iSelection, MF_CHECKED) ;
窗口类别中的背景颜色于是被替换为新的背景颜色,窗口显示区域变为无效状态,Windows使用新的背景颜色清除窗口。
Timer弹出式菜单列出了两个选项-「Start」和「Stop」。开始时,「Stop」选项变为灰色的(就像在资源描述档中的菜单定义一样)。当您选择「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程序会停止计数,启用「 Start」项,然后无效化「Stop」选项:
EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_GRAYED) ;
请注意,在定时器执行时,MENUDEMO程序不可能收到wParam等于IDM_TIMER_START的WM_COMMAND消息。同样地,在定时器关闭时,MENUDEMO程序也不可能收到wParam等于IDM_TIMER_STOP的WM_COMMAND消息。
当MENUDEMO收到一个WM_COMMAND消息,而该消息的参数wParam等于IDM_APP_ABOUT或IDM_APP_HELP时,MENUDEMO程序显示一个消息框(在下一章中,我们将把消息框变为对话框)。
当MENUDEMO程序收到一个WM_COMMAND消息,其参数wParam等于IDM_APP_EXIT时,它给自己发送一个WM_CLOSE消息。这个消息与DefWindowProc收到WM_SYSCOMMAND消息且wParam等于SC_CLOSE时发送给窗口消息处理程序的消息相同。我们将在本章后面介绍 POPPAD2时再仔细研究这个问题。