摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P350
你还记得蟒蛇剧团表演的关于奶酪商店的讽刺剧吗?剧情是这样的:一个人来到奶酪商店,想买某种奶酪。商店没有这种奶酪。所以他要另一种奶酪,再要另一种,再一种(最后总共 40 多种,大多数都很罕见。),而答案始终是“没有,没有,没有,没有,没有,没有”。到最后,事件发展成了抢击案。
如果使用菜单,整个不幸事件的发生也许就可以避免。菜单是一个可用选项的列表。菜单告诉一个饥饿的顾客厨房可以提供哪些食品。对 Windows 程序而言就是告诉用户一个应用程序能执行哪些操作。
菜单可能是 Windows 程序提供的一致的用户界面中最重要的部分,而向程序中添加菜单是 Windows 编程中相对容易的部分。你在 Developer Studio 中定义菜单。每个可选菜单项被赋予一个唯一的 ID 号。你在窗口类结构中指定菜单的名字。当用户选择菜单项时,Windows 向你的程序发送一个含有该菜单项 ID 的 WM_COMMAND 消息。
在讨论完菜单之后,我将以有关键盘加速键的一节来结束本章,键盘加速键是多个键的组合,主要用来模拟菜单功能。
10.2.1 和菜单有关的概念
窗口的菜单栏紧挨着标题栏下面显示。这个菜单栏有时叫作程序的“主菜单”或“顶级菜单”(top-level menu)。顶级菜单中的菜单项通常会激活下拉菜单(drop-down menu),也叫“弹出菜单”(popup menu)或“子菜单”(submenu)。你可以定义多级嵌套的弹出菜单:一个弹出菜单项可以激活另一个弹出菜单。有时弹出菜单可以激活对话框来提供更多信息。许多父窗口在标题栏的最左边显示程序的小图标。这个图标会激活系统菜单,该菜单实际上是另一种弹出菜单。
弹出菜单的菜单项可以被“选中”(checked),亦即 Windows 在菜单文本左边显示一个小的选中标记。选中标记的使用让用户可以选择不同的程序选项。这些选项可以使互相排斥的,当然不是必须要这样做。顶级菜单项不能被选中。
顶级菜单或弹出菜单的菜单项可以被“启动”(enabled)、“禁用”(disabled)或“变灰”(grayed)。单词“活动”(Active)和“非活动”(Inactive)有时可以和“启用”和“禁用”同义使用。标记为启用或禁用的菜单项对用户来讲看起来一样,但变灰菜单项显示为灰色文本。
从用户的角度看,启用、禁用或变灰的菜单项都能被“选择”(加亮)。也就是说,用户可以在禁用的菜单项上单击鼠标,或者将反色显示(reverse-video)的光标条移动到一个禁用的菜单项,或者使用菜单项的键字母来除法禁用的菜单项。然而,从程序的角度,启用、禁用或变灰的菜单项功能不同。Windows 只向被启用的菜单项发送 WM_COMMAND 消息。对当前无效的菜单选项你可使用禁用或变灰的办法。如果你想让用户知道选项是无效的,最好将它变灰。
10.2.2 菜单结构
在程序中创建或修改菜单时,将顶级菜单和每个弹出菜单想象成独立的菜单会有利于理解。顶级菜单有一个菜单句柄,在顶级菜单中的每个弹出菜单也都有自己的菜单句柄,系统菜单(也是一个弹出菜单)也有一个菜单句柄。
每个菜单项由三个特征定义。第一个特征是菜单显示什么。这可以使一个文本字符串或是一个位图。第二个特征是一个 ID 号或一个指向弹出菜单的句柄,Windows 会在 WM_COMMAND 消息中把 ID 号发送给你的程序,而弹出菜单则在用户选择该菜单项时由 Windows 显示出来。第三个特征描述了菜单项的属性,包括该菜单项是否被禁用、变灰或选中。
10.2.3 定义菜单
要使用 Developer Studio 给程序的资源脚本加入菜单,应从 Insert 菜单中选择 Resource,然后选择 Menu。(当然你可能已经知道这些步骤了。)然后你就可以交互式定义菜单。菜单中的每一项都有一个相关联的 Menu Item Properties 对话框,用来指示菜单项的文本字符串。如果Pop-up 框被选中,则该菜单项会激活一个弹出菜单,这时它没有相关联的 ID。如果 Pop-up 框没有被选中,那么该菜单项会生成一个带有特定 ID 的 WM_COMMAND 消息。这两种类型的菜单项会分别以 POPUP 和 MENUITEM 语句的形式在资源脚本中出现。
在为菜单中的一项输入文本时,可以输入一个符号 & 来指示 Windows 在显示菜单时给紧接着 & 的下一个字符显示下划线。用 Alt 键选择一个菜单项时,Windows 就寻找这样一个带有下划线的字符。如果不在文本中包含字符 &,下划线就不会出现,Windows 将会用菜单项的第一个字母来进行 Alt 键搜索。
如果在 Menu Items Properties 对话框中选择了 Grayed 选项,则表示该菜单项是非活动的,它的文本会变灰,并且不会产生 WM_COMMAND 消息。如果选择 Inactive 选项,则表示该菜单项是非活动的,不会产生 WM_COMMAND 消息,但是它的文本会被正常显示。Checked 选择会再菜单项的旁边加一个复选标记。Separator 选项会再菜单上绘制一条水平的分隔线。
对弹出菜单中的菜单项,可以在字符串中使用分栏制表符\t。即使弹出菜单第一栏的字符串很长,\t 后面的文本也会被放置在右边足够远的新一栏中。在本章末尾,当我们学习键盘加速键时,我们会看到它是如何工作的。字符串中的\a 会将它后面的文本进行右对齐。
指定的 ID 值是 Windows 在菜单消息中发送给窗口过程的数字。ID 值在一个菜单中应该是唯一的。按照惯例,我使用以IDM(ID for a Menu)开头的标识符。
10.2.4 在程序中引用菜单
大多数 Windows 应用程序在资源脚本中只有一个菜单。可以给该菜单指定一个与程序名一样的文本名字。程序员经常使用程序名作为菜单名,这样同一个字符串可以作为窗口类名、程序图标名和菜单名。然后程序可以在窗口类定义中引用这个菜单:
wndclass.lpszMenuName = szAppName;
虽然在窗口类中指定菜单是引用菜单资源的最通用的方法,但它不是唯一的方法。Windows 应用程序可以用 LoadMenu 函数把菜单资源加载到内存,这和前面描述的 LoadIcon 和 LoadCursor 函数非常类似。LoadMenu 返回一个菜单的句柄。如果在资源脚本中为菜单设定了一个名字,那么该语句看起来会像这样:
hMenu = LoadMenu(hInstance, TEXT("MyMenu"));
如果使用数字,那么 LoadMenu 调用格式如下:
hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(ID_MENU));
之后便可以把这个菜单句柄指定为 CreateWindow 的第 9 个参数:
hMenu = CreateWindow (TEXT("MyClass"), TEXT("Window Caption"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenu, hInstance, NULL);
这种情况下,CreateWindow 中指定的菜单会覆盖窗口类中指定的任何菜单。如果 CreateWindow 的第 9个参数是 NULL,那么你可以认为窗口类的菜单是基于该窗口类的所有窗口的默认菜单。因此,你可以对基于同一个窗口类的几个不同窗口使用不同的菜单。
还可以在窗口类中指定一个 NULL 菜单名,并在 CreateWindow 调用中使用一个 NULL 菜单句柄,然后在窗口创建之后再给它指派一个菜单:
SetMenu (hwnd, hMenu);
这种形式可以让你动态地改变窗口的菜单。我们会再本章后面的 NOPOPUPS 程序中看到这样的一个例子。
当窗口被销毁时,附加到该窗口的任何菜单也将被销毁。而在程序结束前,任何没有附加到窗口的菜单应该通过 DestroyMenu 调用被显示地销毁。
10.2.5 菜单和消息
当用户选择一个菜单项时,Windows 通常会向窗口过程发送几个不同的消息。多数情况下,应用程序可以忽略这些消息中的大部分,并将它们简单地传递给 DefWindowProc。这些消息中的一个是带有如下参数的 WM_INITMENU 消息:
wParam: | 主菜单句柄 |
lParam: | 0 |
应用程序还会受到 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。大多数程序向 DefWindowsProc 传递此消息。
Windows 要显示弹出菜单时,会向窗口过程发送一个带有如下参数的 WM_INITMENUPOPUP 消息:
wParam: | 弹出菜单的句柄 |
LOWORD(lParam): | 弹出菜单的索引 |
HIWORD(lParam): | 1 代表系统菜单,0 代表其他菜单 |
最重要的菜单消息是 WM_COMMAND。这个消息表示用户已经从窗口菜单中选择了一个被启用的菜单项。你应该记得在第 8 章中,子窗口控件也可以产生 WM_COMMAND 消息。如果碰巧对菜单和子窗口控件使用了同一的 ID 码,便可以通过检查 lParam 的值来区分它们。对于菜单项而言,这个值是 0。
菜 单 | 控 件 | |
---|---|---|
LOWORD(wParam): | 菜单 ID | 控件 ID |
HIWORD(wParam): | 0 | 通知码 |
lParam: | 0 | 子窗口句柄 |
wParam: | 菜单 ID |
lParam: | 0 |
对于 WM_SYSCOMMAND,菜单 ID 指示系统菜单中的哪一项被选中。对于预定义的系统菜单项,后面 4 位应该与 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 的值。向 DefWindowProc 传递正常的 WM_SYSCOMMAND 消息是很重要的。如果不这样做,实际上时截断了正常的系统菜单命令。
我们查看的最后一个消息是 WM_MENUCHAR,它实际上根本不是一个真正的菜单消息。Windows 在两种情况下给你的窗口过程发送这个消息:如果用户按下了 Alt 和一个不对应于任何菜单项的字符键;或者,当弹出菜单显示时,用户按了一个不对应任何弹出菜单项的字符键。WM_MENUCHAR 消息携带的参数如下:
LOWORD(wParam): | 字符码(ASCII 或 Unicode) |
HIWORD(wParam): | 选择码 |
lParam: | 菜单句柄 |
- 0 没有弹出菜单显示
- MF_POPUP 弹出菜单被显示。
- MF_SYSMENU 系统弹出菜单被显示。
Windows 程序通常把这个消息传递给 DefWindowProc,DefWindowProc 一般返回 0 给 Windows,这会使 Windows 发出嘟嘟声。在第 14 章的 GRAFMENU 程序中,我们会看到 WM_MENUCHAR 消息的使用。
10.2.6 范例程序
让我们看一个简单的例子。MENUDEMO 程序的主菜单有五个菜单项——File、Edit、Background、Timer 和 Help。每一个菜单项都有一个弹出菜单。MENUDEMO 进行最简单和最通用的菜单处理,包括捕获 WM_COMMAND 消息以及检查 wParam 的低位字。
/*------------------------------------------------------------
MENUDEOM.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))
{
DWORD error = GetLastError();
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 Demostration Program\n")
TEXT("(c) Charles Petzold, 1998"),
szAppName, MB_ICONEXCLAMATION | 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);
}
MENUDEOM.RC (excerpts)
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Menu
//
MenuDemo MENU
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 "&Gary", 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 (excerpts)
// Microsoft Visual C++ 生成的包含文件。
// 供 MenuDemo.rc 使用
//
#define IDM_BKGND_WHITE 40024
#define IDM_BKGND_LTGRAY 40025
#define IDM_BKGND_GRAY 40026
#define IDM_BKGND_DKGRAY 40027
#define IDM_BKGND_BLACK 40028
#define IDM_EDIT_UNDO 40029
#define IDM_EDIT_CUT 40030
#define IDM_EDIT_COPY 40031
#define IDM_EDIT_PASTE 40032
#define IDM_EDIT_CLEAR 40033
#define IDM_FILE_NEW 40034
#define IDM_FILE_OPEN 40035
#define IDM_FILE_SAVE 40036
#define IDM_FILE_SAVEAS 40037
#define IDM_APP_EXIT 40038
#define IDM_FILE_SAVE_AS 40039
#define IDM_TIMER_START 40042
#define IDM_TIMER_STOP 40043
#define IDM_APP_HELP 40046
#define IDM_APP_ABOUT 40047
MENUDEMO.C 资源脚本应该给了你一些定义菜单的提示。该菜单有一个文本名“MenuDemo”。大多数菜单项都有一个字母带有下划线,意味着必须在该字母前输入符号&。MENUITEM SEPARATOR 语句是在 Menu Item Properties 对话框中选中 Separator 复选框的结果。注意,有一个菜单项有 Checked 选项而另一个有 Grayed 选项。另外,Background 弹出菜单的 5 个菜单项应该按照显示的顺序输入,以保证标识符按数字排序;程序依赖于这一点。所有菜单项标识符都定义在 RESOURCE.H 中。
对 File 和 Edit 弹出菜单中的大多数菜单项,当收到它们的 WM_COMMAND 消息时,MENUDEMO 程序只发出嘟声。Background 弹出菜单列出了 5 个备用画刷,MENUDEMO 将用这些画刷来给背景着色。在 MENUDEOM.RC 资源脚本中,White 菜单项(菜单 ID 是IDM_BKGND_WHITE)被标记为 CHECKED,这会在该菜单项后面加一个选中标记。在 MENUDEMO.C 中,iSelection 的值开始时被设为 IDM_BKGND_WHITE。
Background 弹出菜单中的 5 个画刷是互相排斥的。当 MENUDEMO.C 收到 WM_COMMAND 消息并且 wParam 是 Background 弹出菜单中的 5 个菜单项之一时,它必须从上次选中的背景颜色上去掉选中标记,并在新的背景颜色上加上选中标记。为此,它首先获得菜单的句柄:
hMenu = GetMenu(hwnd);
CheckMenuItem 函数用来取消对当前选中项的选中标记:
CheckMenuItem (hMenu, iSelection, MF_UNCHECKED);
然后 iSelection 值被设为 wParam 的值,同时新的背景颜色被选中:
iSelection = LOWORD(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);
而在收到 wParam 为 IDM_TIMER_STOP 的 WM_COMMAND 消息之后,MENUDEMO 终止计时器,激活 Start 选项,并把 Stop 选项变灰:
KillTimer(hwnd, ID_TIMER);
EnableMenuItem(hMenu, IDM_TIMER_START, MF_ENABLED);
EnableMenuItem(hMenu, IDM_TIMER_STOP, MF_GRAYED);
注意,当计时器正在运行时,MENUDEMO 不可能接收到 wParam 为 IDM_TIMER_START 的 WM_COMMAND 消息。同一,当计时器没有运行时,也不可能接收到 wParam 为 IDM_TIMER_STOP 的 WM_COMMAND 消息。
当 MENUDEMO 接收到 wParam 参数为 IDM_APP_ABOUT 或 IDM_APP_HELP 的 WM_COMMAND 消息时,它显示一个消息框。(在第 11 章,我们将把它改成对话框。)
当 MENUDEMO 接收到 wParam 参数为 IDM_APP_EXIT 的 WM_COMMAND 消息时,它给自己发送一个 WM_CLOSE 消息。这和 DefWindowProc 在收到 wParam 为 SC_CLOSE 的 WM_SYSCOMMAND 消息时发送给窗口过程的消息是一样的。
10.2.7 菜单设计中的规范
MENUDEMO 中 File 和 Edit 弹出菜单的格式和其他 Windows 程序中的非常类似。Windows 的目标之一就是给用户提供一个易于识别的界面,而不需要对每个程序都重新学习基本概念。如果每个 Windows 程序的 File 和 Edit 菜单看起来都一样,都使用同样的字母和 Alt 组合键来进行选择,这当然很有帮助。
除了 File 和 Edit 弹出菜单,大多数 Windows 程序的菜单可能会不同。在设计菜单时,你应该查看现有的 Windows 程序并以保持大体一致为目标。当然,如果你认为其他程序都做错了而只有你知道正确的方法,那也没人会阻止你去使用正确的方法。还要记住修订菜单通常仅需要修改资源脚本而不需要修改你的程序代码。此后你可以在菜单项之间来回移动而不会碰到任何问题。
虽然程序可以在顶级菜单中包含 MENUITEM 语句,但这样并不常见,因为它们很容易被错误地选中。如果你需要这样做,建议在文本字符串后加一个惊叹号,以表示该菜单不会调用弹出菜单。
10.2.8 定义菜单的繁琐方式
通常, 在程序的资源脚本中定义菜单是给你的程序加入菜单最容易的方式,但不是唯一的方式。你可以撇开资源脚本,完全通过 CreateMenu 和 AppendMenu 这两个函数在你的程序中创建菜单。在完成菜单定义后,你可以将菜单句柄传递给 CreateWindow 或使用 SetMenu 来设定窗口菜单。下面是具体的实现方法。CreateMenu 只返回一个新菜单的句柄:
hMenu = CreateMenu();
该菜单刚开始时是空的。AppendMenu 函数会把菜单项添加到菜单中。对顶级菜单项和每个弹出菜单,你必须使用不同的菜单句柄。弹出菜单是另外生成的;弹出菜单句柄稍后被插入到顶级菜单。下面的代码就通过这种方式生成了一个菜单;事实上,这和我在 MENUDEMO 程序中使用的菜单完全相同。
hMenu = CreateMenu();
hMenuPopup = CreateMenu();
AppendMenu(hMenuPopup, MF_STRING, IDM_FILE_NEW, TEXT("&New"));
AppendMenu(hMenuPopup, MF_STRING, IDM_FILE_OPEN, TEXT("&Open..."));
AppendMenu(hMenuPopup, MF_STRING, IDM_FILE_SAVE, TEXT("&Save"));
AppendMenu(hMenuPopup, MF_STRING, IDM_FILE_SAVE_AS, TEXT("Save &As..."));
AppendMenu(hMenuPopup, MF_SEPARATOR, 0, NULL);
AppendMenu(hMenuPopup, MF_STRING, IDM_APP_EXIT, TEXT("E&xit"));
AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPopup, TEXT("&File"));
hMenuPopup = CreateMenu();
AppendMenu(hMenuPopup, MF_STRING, IDM_EDIT_UNDO, TEXT("&Undo"));
AppendMenu(hMenuPopup, MF_SEPARATOR, 0, NULL);
AppendMenu(hMenuPopup, MF_STRING, IDM_EDIT_CUT, TEXT("Cu&t"));
AppendMenu(hMenuPopup, MF_STRING, IDM_EDIT_COPY, TEXT("&Copy"));
AppendMenu(hMenuPopup, MF_STRING, IDM_EDIT_PASTE, TEXT("&Paste"));
AppendMenu(hMenuPopup, MF_STRING, IDM_EDIT_CLEAR, TEXT("De&lete"));
AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPopup, TEXT("&Edit"));
hMenuPopup = CreateMenu();
AppendMenu(hMenuPopup, MF_STRING | MF_CHECKED, IDM_BKGND_WHITE, TEXT("&White"));
AppendMenu(hMenuPopup, MF_STRING, IDM_BKGND_LTGRAY, TEXT("&Light Gray"));
AppendMenu(hMenuPopup, MF_STRING, IDM_BKGND_GRAY, TEXT("&Gray"));
AppendMenu(hMenuPopup, MF_STRING, IDM_BKGND_DKGRAY, TEXT("&Dark Gray"));
AppendMenu(hMenuPopup, MF_STRING, IDM_BKGND_BLACK, TEXT("&Black"));
AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPopup, TEXT("&Background"));
hMenuPopup = CreateMenu();
AppendMenu(hMenuPopup, MF_STRING, IDM_TIMER_START, TEXT("&Start"));
AppendMenu(hMenuPopup, MF_STRING | MF_GRAYED, IDM_TIMER_STOP, TEXT("&Stop"));
AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPopup, TEXT("&Timer"));
hMenuPopup = CreateMenu();
AppendMenu(hMenuPopup, MF_STRING, IDM_APP_HELP, TEXT("&Help"));
AppendMenu(hMenuPopup, MF_STRING, IDM_APP_ABOUT, TEXT("&About MenuDemo..."));
AppendMenu(hMenu, MF_POPUP, (UINT)hMenuPopup, TEXT("&Help"));
我认为你会同意用资源脚本菜单模板来定义菜单更容易更清楚。这里我不建议你用前面这种代码的方法来定义菜单,我这样做的目的只是想展示一种实现方法。当然,可以用结构数组来存储所有菜单项字符串、ID 和标志,从而极大地缩小源代码大小。但如果这样做,还不如利用 Windows 提供的定义菜单的第三个办法。LoadMenuIndirect 函数接收一个指向 MENUITEMPLATE 类型的结构的指针,并返回一个指向菜单的句柄。当通常的菜单模板从资源脚本加载后,这个函数被 Windows 用来构件菜单。如果你很勇敢,可以亲自尝试一下。
10.2.9 浮动弹出菜单
即使没有顶级菜单栏,也可以使用菜单。你可以让弹出菜单出现在屏幕的任何地方。一种途径是作为对鼠标右键单击的响应来激活相应的弹出菜单。POPMENU 程序显示了如何实现这种功能。
/*----------------------------------------
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, 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;
}
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.C 资源脚本定义了一个和 MENUDEMO.RC 中定义的类似的菜单。不同之处在于顶级菜单质保一个菜单项——名字为“MyMenu”的弹出菜单,它可以调用 File、Edit、Background 和 Help 选项。这四个选项在弹出菜单中垂直排列,而不像主菜单那样水平排列。
在 WndProc 的 WM_CREATE 消息中,POPMENU 获得第一个弹出菜单的句柄——即名为“MyMenu”的弹出菜单的句柄:
hMenu = LoadMenu(hInst, szAppName);
hMenu = GetSubMenu(hMenu, 0);
在 WM_RBUTTONUP 消息中,POPMENU 获得鼠标指针的位置,将其转换为屏幕坐标,并将该坐标传递给 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。选择这些菜单项中的任何一个都会再右边显示一个嵌套弹出菜单。这些菜单和普通菜单的功能一样。
如果想在 TrackPopupMenu 中使用和程序的主菜单相同的菜单,则会遇到一点问题,因为这个函数需要的是一个弹出菜单句柄。Microsoft 知识库 Q99806 提供了一种变通办法。
10.2.10 使用系统菜单
若使用包含 WS_SYSMENU 的样式创建父窗口,在标题栏的左边就会有一个系统菜单框。如果你愿意,可以修改此菜单来加入你自己的菜单命令。在早期 Windows 中,程序通常会在系统菜单中加入“About”菜单项。
修改系统菜单在现今并不常见,但它仍是小程序中加入菜单而无需定义资源脚本的应急方法。唯一的限制是:你
用来向系统菜单加入命令的 ID 号必须小于 0xF000。否则,它们会和 Windows 给标准系统菜单命令使用的 ID 产生冲突。记住,在你的窗口过程中处理针对这些新菜单命令的 WM_SYSCOMMAND 消息时,你必须把其他 WM_SYSCOMMAND 消息传递给 DefWindowProc。不然的话,你就屏蔽了系统菜单的所有正常选项。
POORMENU(“穷人的菜单”)程序,向系统菜单中加入了分割线和三个命令。这些命令中的最后一个会去掉新加入的所有命令。
/*----------------------------------------
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);
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_SYS_ABOUT 1
#define IDM_SYS_HELP 2
#define IDM_SYS_REMOVE 3
创建了程序的窗口后,POORMENU 得到了系统菜单的句柄:
hMenu = GetSystemMenu(hwnd, FALSE);
当第一次调用 GetSystemMenu 函数时,你应该将第二个参数设为 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 去掉这些附加的菜单项。它是通过再次调用 GetSystemMenu 函数并将其第二个参数设为 TRUE 实现的:
GetSystemMenu(hwnd, TRUE);
标准系统菜单有如下选项:Restore、Move、Size、Minimize、Maximize 和 Close。它们会产生 WM_SYSCOMMAND 消息,该消息的 wParam 分别为 SC_RESTORE、SC_MOVE、SC_SIZE、SC_MINIMUM、SC_MAXIMUM 和 SC_CLOSE。虽然 Windows 程序通常并不会这样做,但你确实可以自己处理这些消息,而不是将它们传递给 DefWindowProc。你也可以用下面描述的方法从系统菜单中禁用或去除一些标准菜单项。Windows 文档也包含一些对系统菜单的标准附加选项。它们使用 SC_NEXTWINDOW、SC_PREVIEWWINDOW、SC_VSCROLL、SC_HSCROLL 和 SC_ARRANGE 标识符。你会发现对于某些应用程序,将这些命令加入系统菜单是合适的。
10.2.11 改变菜单
我们已经见过如何使用 AppendMenu 函数给程序定义菜单和向系统菜单中加入菜单项。在 Windows 3.0 之前,你会被迫使用 ChangeMenu 函数来完成这个任务。ChangeMenu 的用途非常广泛,以致于它是 Windows 中最复杂的函数之一(至少现在是)。时代在变化。许多当前的函数现在比 ChangeMenu 还要复杂,ChangeMenu 已经被五个新函数代替:
- AppendMenu 在菜单末尾加入一个新菜单项
- DeleteMenu 向菜单中删除已存在菜单项并销毁它
- InsertMenu 向菜单中插入一个新菜单项
- ModifyMenu 修改一个已存在的菜单项
- RemoveMenu 从菜单中去除一个已有的菜单项
如果菜单项是一个弹出菜单,了解 DeleteMenu 和 RemoveMenu 的区别很重要。DeleteMenu 会销毁该弹出菜单而 RemoveMenu 不会。
10.2.12 其他菜单命令
在本节,你会发现更多和菜单相关的有用函数。
当你改变一个顶级菜单项时,该改动直到 Windows 重绘菜单栏时才会被显示出来。你可以调用下面的语句来强制重绘:
DrawMenuBar (hwnd);
注意,DrawMenuBar 的参数是一个指向窗口的句柄,而非菜单就句柄。
可以使用如下语句来获得弹出菜单的句柄:
hMenuPopup = GetSubMenu(hMenu, iPosition);
其中 iPosition 是弹出菜单在顶级菜单中的索引(从 0 开始),hMenu 代表顶级菜单。然后便可以在其他函数(例如 AppendMenu)中使用该弹出菜单句柄。
使用如下语句,可以获得顶级菜单或弹出菜单中现有菜单项的数目:
iCount = GetMenuItemCount(hMenu);
可以使用下面的语句来获得弹出菜单中某个菜单项的菜单 ID:
id = GetMenuItemID (hMenuPopup, iPosition);
其中 iPosition 是该菜单项在弹出菜单中的位置(从 0 开始)。
在 MENUDEMO 中,展示了如何使用下面的语句在弹出菜单中“选中”和“取消选中” 某个菜单项。
CheckMenuItem (hMenu, id, iCheck);
在 MENUDEMO 中,hMenu 是顶级菜单的句柄,id 是菜单 ID,iCheck 的值是 MF_CHECKED 或 MF_UNCHECKED。
如果hMenu 是弹出菜单的句柄,那么 id 参数可以是位置索引而非菜单 ID。如果使用索引更加方便,那么你可以在第三个参数中包含 MF_BYPOSITION:
CheckMenuItem (hMenu, iPosition, MF_CHECKED | MF_BYPOSITION);
EnableMenuItem 和 CheckMenuItem 函数类似,不同之处在于第三个参数是 MF_ENABLED、MF_DISABLED 或 MF_GRAYED。如果你在顶级菜单项上使用 EnableMenuItem,而该菜单项还有弹出菜单,那么你必须在第三个参数中使用 MF_BYPOSITION 标识符,因为该菜单项没有菜单 ID。我们会再本章后面的 POPPAD2 程序中看到 EnableMenuItem 函数的示例。HiliteMenuItem 函数和 CheckMenuItem 以及 EnableMenuItem 类似,但它使用标志 MF_HILITE 和 MF_UNHILITE。这种加亮使用的是反色显示(Rverse Video),在你在菜单项之间移动时 Windows 使用的就是这种加亮形式。通常情况下不需要使用 HiliteMenuItem。
还需要在菜单中做些什么呢?你是否忘记了你在菜单中使用的是什么字符串?你可以调用下面的语句:
iCharCount = GetMenuString(hMenu, id, pString, iMaxCount, iFlag);
iFlag 可以是 MF_BYCOMMAND(此时 id 是一个菜单 ID)或者 MF_BYPOSITION(此时 id 是一个位置索引)。该函数复制至多 iMaxCount 个字符到 pString,并返回复制的字符数。
或者你想知道一个菜单项的当前标志,可以调用:
iFlags = GetMenuState(hMenu, id, iFlag);
同样,iFlag 是 MF_BYCOMMAND 或 MF_BYPOSITION 之一。iFlag 参数是所有当前标志的组合值。 你可以针对 MF_DISABLED、MF_GRAYED、MF_CHECKED、MF_MENUBREAK、MF_MENUBARBREAK 和 MF_SEPARATOR 标识符进行检测,以确定当前的标志。
或者此时你也许有点厌倦菜单了。这种情况下,你会高兴地知道当应用程序不再需要菜单时,通过下面的语句可以销毁它:
DestroyMenu (hMenu);
这个函数使菜单句柄无效。
10.2.13 菜单的另类用法
现在让我们稍微偏离正题。与其在程序中使用下拉菜单,我们是否可以创建没有弹出菜单的多层顶级菜单并用 SetMenu 在顶级菜单之间切换呢?这样的一个菜单可能会唤起早期程序员对经典字符模式菜单的回忆,如 Lotus 1-2-3 中的菜单。NOPOPUPS 程序展示了如何实现这个功能。这个程序包含了类似于 MENUDEMO 中的 File 和 Edit 菜单项,但它们被显示在替换的顶级菜单中。
/*---------------------------------------------------
NOPOPUS.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 Demostration"),
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 (excerpts)
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
/
//
// Menu
//
MENUMAIN MENU
BEGIN
MENUITEM "MAIN:", 0, INACTIVE
MENUITEM "&Fille...", IDM_FILE
MENUITEM "&Edit...", IDM_EDIT
END
MENUFILE MENU
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
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 (excerpts)
// Microsoft Visual C++ 生成的包含文件。
// 供 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 将每个菜单资源加载到内存:
hMenuMain = LoadMenu(hInstance, TEXT("MenuMain"));
hMenuFile = LoadMenu(hInstance, TEXT("MenuFile"));
hMenuEdit = LoadMenu(hInstance, TEXT("MenuEdit"));
开始时,程序显示主菜单:
SetMenu(hwnd, hMenuMain);
主菜单使用字符串显示三个选项:主菜单“MAIN:”、文件菜单“File...”和编辑菜单“Edit...”。然而,“MAIN:”菜单被禁用了,所以它不会触发 WM_COMMAND 消息给窗口过程。File 和 Edit 菜单分别以 “FILE:”和“EDIT:”开头,以此来标识其为子菜单。每个菜单的最后一个菜单项是字符串“(Main)”;这个菜单项用来返回主菜单。在这三个菜单之间切换很简单:
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;
[其他程序行]
}
break;
在处理 WM_DESTROY 消息时,NOPOPUS 将程序的菜单设置为主菜单,并调用 DestroyMenu 销毁 File 和 Edit 菜单。当窗口被销毁时,主菜单会被自动销毁。