1、1第 3 章 界面编程 1-菜单Windows 应用程序开发的基础是 GUI 的设计与编程,GUI 包括 Windows API 提供的窗口、菜单、对话框和控件等基本界面元素,以及 MFC 提供的工具栏和状态栏等组合控件,还有键盘和鼠标等消息响应与事件驱动。这些内容将在本书的第 37 章中逐个加以讨论,本章介绍菜单的设计与编程。3.1 菜单菜单(menu)是传统 GUI 程序的主要命令接口,可用键盘和鼠标来访问。虽然菜单是基于文字串的,但是相对于(可视方便的)工具条和(直接快速的)快捷键,菜单所提供的程序功能更为完整和全面。默认的传统 MFC 应用程序,会在主框架窗口中自动生成一个菜单条( m
2、enu bar) 。菜单条也叫顶层菜单(top-level menu) ,一般包含文件、编辑、查看、窗口、帮助等若干下拉菜单。3.1.1 菜单与菜单项菜单可分为包含若干菜单项的弹出菜单和发送命令消息的菜单项两大类,可以用键盘和鼠标这两种输入设备来访问菜单和菜单项(简记为“菜单 项 ”) 。1菜单弹出菜单(pop-up menu,简称为菜单)包含若干菜单项,一般处于(诸菜单项都不可见的)关闭状态。只有在用户(通过菜单名或按鼠标右键)激活菜单时,才由操作系统弹出来显示。并在用户通过选中菜单中的某个菜单项而发送对应的命令消息之后,又被操作系统自动关闭。(弹出)菜单又可以进一步分成(菜单条中的)下拉菜
3、单(drop-down menu)和(按鼠标右键后在光标处弹出的)快捷菜单(shortcut menu) ,参见图 3-1 a)。弹出菜单中还可以包含若干(多级)子菜单,由其对应的(右端带 的)菜单项激活,参见图 3-1 b)。2a) 弹出菜单的种类 b) 多级子菜单图 3-1 菜单与菜单项2菜单项菜单项(menu item)用于发送程序的命令消息,一般由位于左端的图标或(表示选中的)勾符 、简述命令功能的标题(caption)串、唯一标识资源的 ID、详细说明命令功能的提示串、对应的加速键资源和事件处理函数等多个部分组成。其中的标题字符串又可包括:菜单名串、访问键、加速键指示串、 (表示会弹
4、出一个对话框)省略号“.”和(由系统在弹出菜单时自动生成的,表示对应于子菜单的右向黑三角) 等内容,例如“ 打开( O) Ctrl+O”、 “ 工具栏 (T)”、 “类型( T) ”等,参见图 3-1。3ID菜单项和其他所有的 Windows 资源一样,都有 ID(IDentifier,标识符) 。ID 是一个(在全程序中具有唯一性的)非负整数,用于识别资源,供程序代码使用。在 Windows 编程中,为了便于人类阅读,ID 通常是用(以下划线分隔的大写字母单词串组成的)符号常量来表示,如 ID_FILE_OPEN。不过,在 MFC 编程中,一般不需要程序员手工编写符号常量 ID 的#defi
5、ne 宏定义,而是在程序员设置资源的 ID 属性时,由资源编辑器自动生成。MFC 应用程序中的所有 ID 定义,都位于项目目录下的资源头文件 resource.h 内。4菜单类与对象在 MFC 类库中,菜单条和菜单都由 CMenu 类表示,它是对 Windows 菜单对象HMENU 的类封装。CMenu 类是直接从 CObject 的类派生的:3CObjectCMenu但是在 MFC 编程中一般不需要直接使用该类,除非你需要动态修改菜单 项的内容。菜单对象属于主框架窗口,可先用从基类 CWnd 继承来的 GetMenu 函数CMenu* GetMenu( ) const;获得菜单条对象的指针(
6、只是临时的,不能保存供以后使用) ,再用菜单类的成员函数GetSubMenuCMenu* GetSubMenu( int nPos ) const;来获取指定的下拉式弹出菜单,其中 nPos 为菜单(从 0 开始的)位置序号。若想在视图类中获得程序的菜单对象,则必须先得到主框架窗口的指针,这可以使用从基类 CWnd 继承来的 GetTopLevelFrame 成员函数:CFrameWnd* GetTopLevelFrame( ) const;例如:CFrameWnd *pFrmWnd = GetTopLevelFrame(); / 获取主框架窗口CMenu *pMenu = pFrmWnd-G
7、etMenu(); / 获取菜单条CMenu *pSubMenu = pMenu-GetSubMenu(3); / 获取第 4 个菜单3.1.2 访问键和快捷键访问键和快捷键都可用于菜单的快速键盘访问。1访问键访问键(mnemonic key,助记键 )也叫热键(hot key)是菜单项标题字符串中出现的(可用对应的键盘按键快速访问的)带下划线的字符(一般为大写英文字母) ,例如菜单条上“文件( F)”菜单中的字母“F”和文件菜单中的“另存为 (A)”菜单项的字母“A” 。访问键的使用方法有如下两种: 按“Alt+访问键 ”,可“ 选中”当前窗口菜单条上访问键所对应的菜单项,例如按“Alt+F
8、”会打开 “文件( F)”菜单。 按“访问键”本身,可“选中”当前已展开菜单中其所对应的菜单项,例如在已经打开的文件菜单中,按“A”会选中“另存为( A).”菜单项,从而打开对应的“另存为”对话框。42快捷键快捷键(shortcut key)又叫加速键(accelerator key) ,是由菜单项标题串右端指明的功能键组合,一般为 Ctrl/Alt/Shift+大写英文字母或功能键 Fn。用快捷键可以直接访问当前窗口的任意菜单项(不论其所在的子孙 菜单是否被展开) ,例如按 “Ctrl+O”可以直接选中(尚未打开的)文件菜单中的“打开(O ). Ctrl+O”菜单项以打开对应的“打开”对话框
9、。需要注意的是,在 MFC 编程中,菜单项的快捷键并不是由其标题中的指示串决定的,而是由项目的加速键资源决定的。因此,在设置完菜单项的各种属性之后,程序员还必须使用 IDE 的快捷键编辑器来将二者关联在一起。3.1.3 默认菜单用 MFC 应用程序向导建立应用程序时,若选择的是单文档或多文档类型,而不是基于对话框类型,则会自动建立预定义的菜单条。1单文档程序传统的单文档界面 MFC 应用程序只有一个菜单条,默认时含 “文件” 、 “编辑” 、 “查看” 、 “帮助”4 个下拉菜单(参见图 3-1 a) ) ,菜单条的 ID 为 IDR_MAINFRAME。2多文档程序传统的多文档界面 MFC
10、应用程序有两个菜单条: 主框架菜单条:在程序刚启动或没有任何文档子窗口时显示,默认时含“文件” 、“查看” 、 “帮助”3 个下拉菜单,菜单条的 ID 也为 IDR_MAINFRAME。 文档菜单条:在程序有文档子窗口时显示,默认时含“文件” 、 “编辑” 、 “查看” 、“窗口” 、 “帮助”5 个下拉菜单(增加了一个“窗口”下拉菜单,参见图 2-37) ,ID 为 IDR_*TYPE,其中*表示应用程序的项目名,例如 IDR_TestTYPE。3.1.4 菜单资源在 MFC 应用程序项目中,菜单资源和其他资源一样,都是以文本方式存在于资源脚本文件中,而对应的资源 ID 则被定义在资源头文件
11、中。5在默认情况下,VC 以可视的“资源编辑器”方式来打开资源文件,让程序员进行交互式菜单设计。用资源编辑器编辑菜单等资源的结果,都会被自动保存在这两个资源文件之中。1查看资源文本资源文件指位于 MFC 应用程序项目目录中的资源脚本文件 “项目名.rc” (如Student.rc)和资源头文件 Resource.h。似 C+头文件和代码文件,也可以用任何文本编辑器(如 Windows 自带的记事本和写字板) ,来可以查看和编辑资源头文件和资源脚本文件。也可以用 VC 来查看和编辑资源文件中的源代码文本。在 VC 中,以文本方式来打开资源文件的做法有三种(都需先关闭资源编辑器): 方法 1: 在
12、项目区中选中“解决方案资源管理器”页,选中应用程序的“资源文件*.rc”。 按鼠标右键,在弹出的快捷菜单中,选“查看代码”菜单项,参见图 3-2 a)。 方法 2: 在项目区中选中“解决方案资源管理器”页,选中应用程序的“资源文件*.rc”。 按鼠标右键,在弹出的快捷菜单中,选“打开方式”菜单项,也参见图 3-2 a)。 在弹出的“打开方式”对话框中,选中“源代码(文本) 编辑器”项,按“确定”关闭对话框,参见图 3-2 b)。6a) 文件快捷菜单 b) 打开方式对话框图 3-2 以文本方式打开资源文件 方法 3: 在“打开文件”对话框中,选“打开”按钮右端的 钮打开下拉式列表,选中其中的“打
13、开方式(W).” 项。 在弹出的“打开方式”对话框中,选中“源代码(文本) 编辑器”项,按“确定”关闭对话框,参见图 3-2 b)。作为参考,下面给出由 MFC 应用程序向导自动生成的 Student 程序的默认资源头文件(Resource.h)和菜单资源文本的内容(位于资源脚本文件 Student.rc 中) 。2资源头文件VC 在资源头文件 Resource.h 中定义各种 ID 符号常量,这些定义由 MFC 应用程序向导或资源编辑器自动生成,一般不需要自己手工修改。下面是 Student 程序的初始资源头文件的内容:/NO_DEPENDENCIES/ Microsoft Visual C
14、+ generated include file./ Used by Student.rc/#define IDD_ABOUTBOX 100#define IDP_OLE_INIT_FAILED 1007#define IDR_MAINFRAME 128#define IDR_StudentTYPE 130/ 新对象的下一组默认值/#ifdef APSTUDIO_INVOKED#ifndef APSTUDIO_READONLY_SYMBOLS#define _APS_NEXT_RESOURCE_VALUE 310#define _APS_NEXT_CONTROL_VALUE 1000#defi
15、ne _APS_NEXT_SYMED_VALUE 310#define _APS_NEXT_COMMAND_VALUE 32771#endif#endif2资源脚本文件在 MFC 项目中,用资源脚本文件“项目名.rc” (后缀 rc = resource,资源)来描述程序的各种资源,包括:包含文本、图标、位图、工具栏、菜单、快捷键、对话框、版本、设计信息、字符串表等资源。下面是 Student 程序的资源脚本文件 Student.rc 中的默认菜单资源部分:/ 菜单/IDR_MAINFRAME MENUBEGINPOPUP “文件(/ TODO: 在此添加专用代码和 /或调用基类4设置菜单项图
16、标使用位图资源来设置菜单项图标的主要步骤为: 创建位图对象(CBitmap):CBitmap *pBmp = new CBitmap(); 装入位图资源(LoadBitmap):pBmp-LoadBitmap(IDB_MATH);29 设置菜单项的标识(SetMenuItemBitmaps): pSubMenu-SetMenuItemBitmaps(ID_SCORE_MATH, MF_BYCOMMAND, pBmp, pBmp);说明:如果需要添加多个菜单项图标,则必须创建多个不同的位图对象,不然会出现系统错误。为了获取应用程序的主框架窗口和菜单条与下拉菜单,还需要使用 3.1.1 小节的第
17、4 部分中介绍的方法与函数。例如,为了给 Student 程序的“成绩数学”菜单项添加标识,可以在上面添加视图类重写型初始化函数 OnInitialUpdate 中的“/ TODO”行后添加如下代码段:CFrameWnd *pFrmWnd = GetTopLevelFrame(); / 获取住框架窗口对象CMenu *pMenu = pFrmWnd-GetMenu(); / 获取菜单条CMenu *pSubMenu = pMenu-GetSubMenu(4); / 获取成绩菜单,菜单序号/ 从 0 开始,具体数值应与你自己菜单条中的一致CBitmap *pBmp = new CBitmap()
18、; / 创建空位图对象pBmp-LoadBitmap(IDB_MATH); / 装入位图资源/ 设置菜单项图标,前一语句使用菜单项 ID,后一注释掉的语句使用菜单项序号pSubMenu-SetMenuItemBitmaps(ID_SCORE_MATH, MF_BYCOMMAND, pBmp, pBmp);/pSubMenu-SetMenuItemBitmaps(0, MF_BYPOSITION, pBmp, pBmp);运行结果参见图 3-23。3.3 响应菜单项当用户选中菜单项时,Windows 会发送一个消息给对应的事件处理程序。Windows 应用程序的主要任务,就是响应用户对各菜单项的
19、选中操作,以完成对应的特定任务。因此,程序员的主要工作,自然就是编写各菜单项对应的事件处理函数。3.3.1 消息映射在传统 API 编程中,Windows 的消息处理是由窗口过程函数来完成的,使用的是带有大量 case 的 switch 语句,参见 2.3.3。由于每个消息的处理都对应于一个 case 语句块,如果要处理的消息众多,或者某些消息的处理代码很多,则会造成庞大和冗长的窗口过程函30数,非常难以编写、修改和维护。为了解决这一问题,MFC 提供了消息映射机制,将原来过程函数中处理每个消息的case 块都映射成一个独立的消息响应函数。这样的消息响应函数一般是某个类的成员函数,除了标准成员函数所需的(在类的头文件中定义的)函数原型和(在类的代码文件中编写的)函数体外,MFC 中的消息映射函数还需在类的代码文件中定义消息映射宏,一般格式为:BEGIN_MESSAGE_MAP(类名, 基类名)ON_COMMAND(ID_, afx_msg void OnMouseMove(UINT nFlags, CPoint point);空函数体如:void CStudentView:OnNameLs()/ TODO: 在此添加命令处理程序代码void CStudentView:OnMouseMove(UINT nFlags, CPoint point)