Download presentation
Presentation is loading. Please wait.
Published byJindřiška Sedláková Modified 5年之前
1
第四章菜单、工具栏和状态栏 4.1菜单 4.2工具栏 4.3状态栏 4.4交互对象的动态更新 4.5综合应用
2
4.1菜单 菜单是一系列命令的列表,通过选中其中的菜单项(命令)来执行相应任务。除一些简单的基于对话框的应用程序外,所有的Windows应用程序都提供了各自的菜单。 为了使应用程序更容易操作,对于菜单系统的设计还遵循下列一些规则: ● 若单击某菜单项后,将弹出一个对话框,那么在该菜单项文本后有“…”。 ● 若某项菜单有子菜单,那么在该菜单项文本后有“”。 ● 若菜单项需要助记符,则用括号将带下划线的字母括起来。助记符与Alt构成一个组合键,当按住“Alt”键不放,再敲击该字母时,对应的菜单项就会被选中。 ● 若某项菜单需要快捷键的支持,则一般将其列在相应菜单项文本之后。所谓“快捷键”是一个组合键,如Ctrl+N,使用时是先按下“Ctrl”健不放,然后再按“N”键。任何时候按下快捷键,相应的菜单命令都会被执行。 图4.1是一个菜单样例,注意它们的规则含义。需要说明的是,在常见的菜单系统中,最上面的一层水平排列的菜单称为顶层菜单,每一个顶层菜单项可以是一个简单的菜单命令,也可以是下拉(Popup)菜单,在下拉菜单中的每一个菜单项也可是菜单命令或下拉菜单,这样一级一级下去可以构造出复杂的菜单系统。
3
4.1菜单 顶层菜单 下拉菜单 图4.1 菜单样例
4
4.1菜单 用编辑器设计菜单 1. 编辑菜单 (1)在顶层菜单的最后一项,Visual C++自动留出了一个空位置,用来输入新的顶层菜单项。在菜单的空位置上双击鼠标左键,出现菜单项的属性对话框,在标题框中输入“测试(&T)”,结果如图4.2所示,其中符号&用来将其后面的字符作为该菜单项的助记符,这样当按住“Alt”键不放,再敲击该助记符键时,对应的菜单项就会被选中,或在菜单打开时,直接按相应的助记符键,对应的菜单项也会被选中。 子菜单的空位置 顶层菜单的空位置 图4.2 Ex_SDI菜单资源
5
4.1菜单 需要说明的是,Visual C++将顶层菜单项的默认属性定义为“弹出”(下拉)菜单,即该菜单项有下拉式子菜单。一个含有下拉子菜单的菜单项是不需要相应的ID标识符。同时,“弹出”菜单项的属性对话框中,ID、分隔符(Separator)和提示(Prompt)项无效。表4.1列出菜单属性对话框“常规”(General)的各项含义。 表4.1 菜单General属性对话框的各项含义 项 目 含 义 ID 菜单的资源ID标识符 标题(Caption) 用来标识菜单项显示文本。助记符字母的前面须有一个&符号,这个字母与Alt构成组合键。 分隔符(Separator) 选中时,菜单项是一个分隔符或是一条水平线 已复选(Checked) 选中时,菜单项文本前显示一个选中标记 弹出(Pop_up) 选中时,菜单项含有一个弹出式子菜单 已变灰(Grayed) 选中时,菜单项显示是灰色的,用户不能选用 非活动(Inactive) 选中时,菜单项没有被激活,用户不能选用 帮助(Help) 选中时,菜单项在程序运行时被放在顶层菜单的最右端 中断(Break,分块) 当为“列”(Column)时,对于顶层菜单上的菜单项来说,被放置在另外一行上,而对于弹出式子菜单的菜单项来说,则被放置在另外一列上;当为“条”(Bar)时,与Column相同,只不过对于弹出式子菜单来说,它还在新列与原来的列之间增加一条竖直线;注意这些效果只能在程序运行后才能看到。 提示(Prompt) 用来指明光标移至该菜单项时在状态栏上显示的提示信息
6
4.1菜单 (2)单击“测试”菜单项下方的空位置,在菜单项属性对话框中,输入标题“切换菜单(&D)”,在ID框输入该菜单项的资源标识符:ID_TEST_CHANGE,结果如图4.3所示。 图4.3 修改菜单项属性 (3)关闭菜单项属性对话框,单击“测试”菜单项并按住鼠标左键不放,移动鼠标,将“测试”菜单项移到“查看”和“帮助”菜单项之间,然后释放鼠标。结果如图4.4所示。这样,就将新添加的“测试”菜单项拖放到“查看”和“帮助”菜单项之间了。需要说明的是,菜单项位置改变后,其属性并没改变。 图4.4 菜单项“测试”拖放后的位置
7
4.1菜单 2. 菜单命令的消息映射 菜单项、工具栏的按钮以及快捷键等用户交互对象都能产生WM_COMMAND命令消息。命令消息能够被文档类、应用类、窗口类以及视图类等多种对象接收、处理,且用户可以用ClassWizard对命令消息进行映射。例如,上述的“切换菜单”菜单项的命令映射过程如下: (1)选择“查看”→“建立类向导”菜单命令或按Ctrl+W快捷键,则出现MFC ClassWizard对话框,并自动切换到Message Maps页面。 (2)从“Class name”列表中选择CMainFrame,在IDs列表中选择ID_TEST_CHANGE,然后在Messages框中选择COMMAND消息。单击[Add Function]按钮或双击COMMAND消息,出现“Add Member Function”对话框以输入成员函数的名称。系统默认的函数名为OnTestChange,如图4.5所示。该函数是对菜单项ID_TEST_CHANGE的映射,也就是说,当应用程序运行后,用户选择“测试”→“切换菜单”菜单时,该函数OnTestChange被调用,执行函数中的代码。
8
4.1菜单 图4.5 菜单命令消息的映射
9
4.1菜单 (3)单击[OK]按钮,在ClassWizard的“Member functions”列表中将列出新增加的成员函数。选择此函数,单击[Edit Code]按钮(或直接在函数名双击鼠标),在此成员函数中添加下列代码: void CMainFrame::OnTestChange() { MessageBox("现在就切换吗?"); } (4)编译运行并测试。在应用程序的顶层菜单上,单击“测试”菜单项,然后将鼠标移动到弹出的子菜单项“切换菜单”上,则结果如图4.6所示,此时状态栏上显示该菜单项的提示信息,该信息就是在前图4.3的菜单项属性对话框“提示”框中设置的内容。单击“切换菜单”,则弹出一个消息对话框,显示内容“现在就切换吗?”。 图4.6 Ex_SDI运行后的菜单
10
4.1菜单 使用键盘快捷键 通过菜单系统,可以选择几乎所有可用的命令和选项,它保证了菜单命令系统的完整性,但菜单系统也有某些美中不足之处,如操作效率不高等。尤其对于那些反复使用的命令,很有必要进一步提高效率,于是加速键应运而生。 加速键也往往被称为键盘快捷键,一个加速键就是一个按键或几个按键的组合,它用于激活特定的命令。加速键也是一种资源,它的显示、编辑过程和菜单相似。 例如下面的过程是为前面菜单项ID_TEST_CHANGE定义一个键盘快捷键: (1)在项目工作区窗口的ResourceView(资源视图)页面中,展开中Accelerator(加速键)的资源项,双击IDR_MAINFRAME,在右侧窗口中出现如图4.7的加速键资源列表。 下端的空行 图4.7 Ex_SDI的加速键资源
11
表4.2 加速键常规(General)属性的各项含义
4.1菜单 (2)建立一个新的加速键时,只要双击加速键列表的最下端的空行,弹出如图4.8所示的“Accel 属性”(Accel Properties)对话框,其中可设置的属性如表4.2所示 图4.8 加速键属性对话框 表4.2 加速键常规(General)属性的各项含义 项 目 含 义 ID 指定资源ID号的列表项,为了能和菜单联用,通常选择某菜单项的ID号 辅助键(Modifiers) 用来确定Ctrl、Alt、Shift是否是构成加速键的组成部分 类型(Type) 用来确定该加速键的值是虚拟键(VirKey)还是ASCII字符键 键(Key) 是指启动加速键的键盘按键 下一键(Next Key Typed) 单击此按钮后,用户操作的任何按键将成为此加速键的键值
12
4.1菜单 (3)在加速键属性对话框中,先选择在Ex_SDI应用程序菜单资源添加的“切换菜单”菜单项ID_TEST_CHANGE作为要联用的加速键的ID标识符,然后单击[下一键]按钮,并按下Ctrl+1作为此加速键的键值。 需要说明的是,为了使其他用户能查看并使用该加速键,还需在相应的菜单项文本后面添加加速键内容。例如,可将ID_TEST_CHANGE菜单项的标题改成“切换菜单 (&C)\tCtrl+1”,其中“\t”是将后面的“Ctrl+1”定位到下一个水平制表位。 (4)编译运行并测试。当程序运行后,按Ctrl+1键将执行相应的菜单命令。 更改应用程序菜单 下面就上节的单文档应用程序Ex_SDI的基础上,为其添加菜单资源并实现应用程序的菜单更改。如下面的过程: (1)按快捷键Ctrl+R,弹出“插入资源”对话框,在资源类型中选定“Menu”,如图4.9。 图4.9 “插入资源”对话框
13
4.1菜单 (2)单击[新建]按钮,系统就会为应用程序添加一个新的菜单资源,并自动赋给它一个默认的标识符名称(第一次为IDR_MENU1,以后依次为IDR_MENU2、IDR_MENU3、...),同时自动打开这个新的菜单资源及相应的菜单编辑器,如图4.10所示。 (3)在Menu资源的ID_MENU1上 右击鼠标,从弹出的快捷菜单中选择“属性” (Properties)命令,出现如图4.11所示的 菜单属性对话框,在这里可以重新指定菜 单资源ID,设置菜单资源的语言和条件, 这个条件用来决定菜单资源包含到哪个环境中, 例如当指定条件为_DEBUG,则菜单资源只 存在于Debug编译环境中。 菜单空位置 菜单默认ID 图4.10 添加菜单资源后的开发环境 图4.11 菜单属性对话框
14
4.1菜单 (4)在菜单的空位置上双击鼠标左键,则出现它的属性对话框。通过其属性对话框为菜单ID_MENU1添加一个顶层弹出菜单项“测试(&T)”,并在该菜单下添加一个子菜单项“返回(&R)”,ID设为ID_TEST_RETURN,如图4.12所示。需要再次强调的是,符号&用来指定后面的字符是一个助记符。 图4.12 设计新的菜单资源 (5)为CMainFrame类添加一个CMenu类型的成员变量m_NewMenu,CMenu类是用来处理菜单的一个MFC类。
15
4.1菜单 (6)按快捷键Ctrl+W打开MFC ClassWizard对话框,切换到Message Maps页面,从“Class name”列表中选择CMainFrame,为菜单项ID_TEST_RETURN添加COMMAND消息映射,使用默认的消息映射函数名,并添中下列代码: void CMainFrame::OnTestReturn() { m_NewMenu.Detach(); // 使菜单对象和菜单句柄分离 m_NewMenu.LoadMenu( IDR_MAINFRAME ); SetMenu(NULL); // 清除应用程序菜单 SetMenu( &m_NewMenu ); // 设置应用程序菜单 } 代码中,LoadMenu和Detach都是CMenu类成员函数,LoadMenu用来装载菜单资源,而Detach是使菜单对象与菜单句柄分离。在调用LoadMenu后,菜单对象m_NewMenu就拥有一个菜单句柄,当再次调用LoadMenu时,由于菜单对象的句柄已经创建,因而会发生运行时错误,但当菜单对象与菜单句柄分离后,就可以再次创建菜单了。SetMenu是CWnd类的一个成员函数,用来设置应用程序的菜单。
16
4.1菜单 (7)修改CMainFrame::OnTestChange中的代码:
void CMainFrame::OnTestChange() { // MessageBox("现在就切换吗?"); m_NewMenu.Detach(); m_NewMenu.LoadMenu( IDR_MENU1 ); SetMenu(NULL); SetMenu( &m_NewMenu ); } (8)编译运行后,选择Ex_SDI应用程序的“测试”→“切换菜单”菜单命令,菜单栏变成了新添加的IDR_MENU1,在新的菜单中选择“测试”→“返回”菜单命令,程序又变回到了原来的主菜单了。
17
4.1菜单 菜单的编程控制 1. 创建菜单 CMenu类的CreateMenu和CreatePopupMenu分别用来创建一个菜单或子菜单框架,它们的原型如下: BOOL CreateMenu( ); // 产生一个空菜单 BOOL CreatePopupMenu( ); // 产生一个空的弹出式子菜单 2. 装入菜单 将菜单从资源装入应用程序中,需调用CMenu成员函数LoadMenu,或者用SetMenu对应用程序菜单进行重新设置。 BOOL LoadMenu( LPCTSTR lpszResourceName ); BOOL LoadMenu( UINT nIDResource ); 其中,lpszResourceName为菜单资源名称,nIDResource为菜单资源ID标识符。 3. 添加菜单项 当菜单创建后,用户可以调用AppendMenu或InsertMenu函数来添加一些菜单项。但每次添加时,AppendMenu是将菜单项添加在菜单的末尾处,而InsertMenu在菜单的指定位置处插入菜单项,并将后面的菜单项依次下移。
18
4.1菜单 BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0,LPCTSTR lpszNewItem = NULL ); BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ); BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ); BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ); 其中,nIDNewItem表示新菜单项的资源ID标识符,lpszNewItem表示新菜单项的内容,pBmp用于菜单项的位图指针,nPosition表示新菜单项要插入的菜单项位置。nFlags表示要增加的新菜单项的状态信息,它的值影响其他参数的含义,如表4.3所示。
19
4.1菜单 表4.3 nFlags的值及其对其他参数的影响 nFlags值 含 义 nPosition值 nIDNewItem值
含 义 nPosition值 nIDNewItem值 lpszNewItem值 MF_BYCOMMAND 菜单项以ID标识符来标识 菜单项资源ID MF_BYPOSITION 菜单项以位置来标识 菜单项的位置 MF_POPUP 菜单项有弹出式子菜单 弹出式菜单句柄 MF_SEPARATOR 分隔线 忽略 MF_OWNERDRAW 自画菜单项 自画所需的数据 MF_STRING 字符串标志 字符串指针 MF_CHECKED 设置菜单项的选中标记 MF_UNCHECKED 取消菜单项的选中标记 MF_DISABLED 禁用菜单项 MF_ENABLED 允许使用菜单项 MF_GRAYED 菜单项灰显
20
4.1菜单 需要注意的是: ● 当nFlags为MF_BYPOSITION时,nPosition表示新菜单项要插入的具体位置,为0时表示第一个菜单项,为 -1时,将菜单项添加菜单的末尾处。 ● nFlags的标志中,可以用“|”(按位或)来组合,例如MF_CHECKED|MF_STRING等。但有些组合是不允许的,例如MF_DISABLED、MF_ENABLED和MF_GRAYED,MF_STRING、MF_OWNERDRAW、MF_SEPARATOR和位图,MF_CHECKED和MF_UNCHECKED 都不能组合在一起。 ● 当菜单项增加、改变或删除后,不管菜单依附的窗口是否改变,都应调用CWnd:: DrawMenuBar来更新菜单。 4. 删除菜单项 调用DeleteMenu函数可将指定的菜单项删除,函数DeleteMenu的原型如下: BOOL DeleteMenu( UINT nPosition, UINT nFlags ); 其中,参数nPosition表示要删除的菜单项位置,它由nFlags进行说明。若当nFlags为MF_BYCOMMAND时,nPosition表示菜单项的ID标识符,而当nFlags为MF_BYPOSITION时,nPosition表示菜单项的位置(第一个菜单项位置为0)。
21
4.1菜单 5. 获取菜单项 下面的3个CMenu成员函数分别获得菜单的项数、菜单项的ID标识符以及弹出式子菜单的句柄。
5. 获取菜单项 下面的3个CMenu成员函数分别获得菜单的项数、菜单项的ID标识符以及弹出式子菜单的句柄。 UINT GetMenuItemCount( ) const; 该函数用来获得菜单的菜单项数,调用失败后返回-1。 UINT GetMenuItemID( int nPos ) const; 该函数用来获得由nPos指定菜单项位置(以0为基数)的菜单项的标识号,若nPos是SEPARATOR(分隔符),则返回-1。 CMenu* GetSubMenu( int nPos ) const; 该函数用来获得指定菜单的弹出式菜单的菜单句柄。该弹出式菜单位置由参数nPos指定,开始的位置为0。若菜单不存在,则创建一个临时的菜单指针。 [例Ex_Menu] 用程序添加并处理一个菜单项 下面的示例过程是利用CMenu成员函数向应用程序菜单中添加并处理一个菜单项: (1)创建一个默认的单文档应用程序Ex_Menu。 (2)选择“查看”→“资源符号”(Resource Symbols)命令,弹出如图4.13所示的“资源符号”对话框,它能对应用程序中的资源标识符进行管理。
22
4.1菜单 图4.13 “资源符号”对话框 菜单项的资源值用资源标识符来表示,是一个好的习惯。在为项目用程序来添加一个菜单项时,为了能使框架使用到这个菜单项,常用此对话框来添加一个新标识符。
23
4.1菜单 (3)单击[新建]按钮,弹出如图4.14所示的“新建符号”(New Symbol)对话框。在“名称”(Name)框中输入一个新的标识符ID_NEW_MENUITEM。在“值”(Value)框中,输入该ID的值,系统要求用户定义的ID值应大于15(0X000F)而小于61440(0XF000)。保留默认的ID值101,单击[确定]按钮,关闭“资源符号”对话框。 图4.14 新标识符对话框
24
4.1菜单 (4)在CMainFrame::OnCreate函数中添加下列代码,该函数在框架窗口创建时自动调用。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... CMenu* pSysMenu = GetMenu(); // 获得程序菜单指针 CMenu* pSubMenu = pSysMenu->GetSubMenu(1); // 获得第二个子菜单的指针 CString strMenuItem("新的菜单项"); pSubMenu->AppendMenu(MF_SEPARATOR); // 增加一水平分隔线 pSubMenu->AppendMenu(MF_STRING,ID_NEW_MENUITEM, strMenuItem); // 在子菜单中增加一菜单项 m_bAutoMenuEnable = FALSE; // 关闭自动更新菜单状态,这样避免添加的菜单项是禁用的 pSysMenu- >EnableMenuItem(ID_NEW_MENUITEM,MF_BYCOMMAND|MF_ENABLED); // 激活菜单项 DrawMenuBar(); // 更新菜单 return 0; }
25
4.1菜单 (5)此时编译运行后,结果如图4.15所示。但此时选择“新的菜单项”命令不会有反应。
图4.15 程序添加的菜单项 (6)用MFC ClassWizard在CMainFrame添加OnCommand消息函数的重载,并添加下列代码: BOOL CMainFrame::OnCommand(WPARAM wParam, LPARAM lParam) { // wParam的低字节表示菜单、控件、加速键的命令ID if (LOWORD(wParam) == ID_NEW_MENUITEM) MessageBox("你选中了新的菜单项"); return CFrameWnd::OnCommand(wParam, lParam); }
26
4.1菜单 (7)编译运行并测试。这时当选择菜单“编辑”→“新的菜单项”命令后,就会弹一个对话框,显示“你选中了新的菜单项”消息。
使用快捷菜单 快捷菜单是一种浮动的弹出式菜单,它是一种较新的用户界面设计风格。当用户按下鼠标右键时,就会相应地弹出一个浮动菜单,提供了几个与当前选择内容相关的菜单选项。 用资源编辑器和MFC库的CMenu::TrackPopupMenu函数可以很容易地创建这样的菜单,CMenu::TrackPopupMenu函数原型如下: BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL ); 该函数用来显示一个浮动的弹出式菜单,其位置由各参数决定。其中,nFlags表示菜单在屏幕显示的位置以及鼠标按钮标志,如表4.4所示。x和y表示菜单的水平坐标和菜单的顶端的垂直坐标。pWnd表示弹出菜单的窗口,此窗口将收到菜单全部的 WM_COMMAND消息。lpRect是一个RECT结构或CRect类指针,它表示一个矩形区域, 用户单击这个区域时,弹出菜单不消失。而当lpRect为NULL时,若用户击在菜单外面, 菜单立刻消失。
27
4.1菜单 表4.4 nFlags的值及其对其他参数的影响 nFlags值 含 义 TPM_CENTERALIGN 屏幕位置标志,表示菜单的水平中心位置由x坐标确定 TPM_LEFTALIGN 屏幕位置标志,表示菜单的左边位置由x坐标确定 TPM_RIGHTALIGN 屏幕位置标志,表示菜单的右边位置由x坐标确定 TPM_LEFTBUTTON 鼠标按钮标志,表示当用户单击鼠标左键时弹出菜单 TPM_RIGHTBUTTON 鼠标按钮标志,表示用户单击鼠标右键时弹出菜单 下面来看一个示例,由于右击鼠标时会向系统发送WM_CONTEXTMENU通知消息,因此通过该通知消息,并在映射函数中添加快捷菜单的相关代码。 [例Ex_ContextMenu] 使用快捷菜单 (1) 创建一个默认的单文档应用程序Ex_ContextMenu。
28
4.1菜单 (2)用MFC ClassWizard在CEx_ContextMenuView类添加WM_CONTEXTMENU消息映射,并在映射函数中添加下列代码: (3)在Ex_ContextMenuView.cpp文件的前面添加CMainFrame类的文件包含: #include "Ex_ContextMenuView.h" #include "MainFrm.h" (4)运行并测试。当用户在应用程序窗口的客户区中右击鼠标,会弹出如图4.16的快捷菜单。 图4.16 快捷菜单
29
4.2工具栏 使用工具栏编辑器 选择菜单“文件”→“打开工作空间(区)”,将前面的单文档应用程序Ex_SDI调入。在项目工作区窗口中选择ResourceView页面,双击“Toolbar”项中的IDR_MAINFRAME,则在主界面的右边出现工具栏编辑器,如图4.17所示。 空按钮 图形工具箱 颜色工具箱 图4.17 工具栏编辑器窗口
30
4.2工具栏 1. 创建一个新的工具栏按钮 在新建的工具栏中,最右端总有一个空按钮,双击该按钮弹出其属性对话框,在ID框中输入其标识符名称,则在其右端又出现一个新的空按钮。单击该按钮,在资源编辑器的工具按钮设计窗口内进行编辑,这个编辑就是绘制一个工具按钮的位图,它同一般图形编辑器操作相同(如Windows系统中的“画图”附件)。 2. 移动一个按钮 在工具栏中移动一个按钮,用鼠标左键点中它并拖动至相应位置即可。若拖动它离开工具栏位置,则此按钮从工具栏中消失。若在移动一个按钮的同时,按下Ctrl键,则在新位置复制一个按钮,新位置可以是同一个工具栏中的其他位置,也可以在不同的工具栏中。 3. 删除一个按钮 前面已提到过,将选取中的按钮拖离工具栏,则该按钮就消失了。但若选中按钮后,单击Delete键并不能删除一个按钮,只是将按钮中的图形全部以背景色填充。 4. 在工具栏中插入空格 在工具栏中插入空格有以下几种情况:
31
4.2工具栏 ● 如果按扭前没有任何空格,拖动该按钮向右移动并当覆盖相邻按钮的一半以上时,释放鼠标键,则此按钮前出现空格。
● 如果按扭前没有任何空格,拖动该按钮向右移动并当覆盖相邻按钮的一半以上时,释放鼠标键,则此按钮前出现空格。 ● 如果按钮前有空格而按钮后没有空格,拖动该按钮向左移动并当按钮的左边界接触到前面按钮时,释放鼠标键,则此按钮后将出现空格。 ● 如果按钮前后均有空格,拖动该按钮向右移动并当接触到相邻按钮时,则此按钮前的空格保留,按钮后的空格消失。相反,拖动该按钮向左移动并当接触到前一个相 邻按钮时,则此按钮前面的空格消失,后面的空格保留。 5. 工具栏按钮属性的设置 双击某按钮弹出其属性对话框,如图4.18所示。属性对话框中的各项说明见表4.5。 图4.18 工具栏按钮属性对话框
32
4.2工具栏 表4.5 工具栏按钮属性的各项含义 项 目 含 义 ID
表4.5 工具栏按钮属性的各项含义 项 目 含 义 ID 工具栏按钮的标识符,用户既可以输入自己的标识符名称,也可从ID框的下拉列表中选取标识符名称 宽度(Width) 工具栏按钮的象素宽度 高度(Height) 工具栏按钮的象素高度 提示(Prompt) 工具栏按钮提示文本;若为“建立新文档\n新建”,则表示将鼠标指向该按钮时,在状态栏中显示“建立新文档”,而在弹出按钮的提示信息中出现“新建”字样。“\n”是它们的分隔转义符
33
4.2工具栏 工具按钮和菜单项相结合 由于按钮与菜单项命令一样,都可以通过MFC ClassWizard来直接映射,因此这里不再重复。这里就工具按钮和菜单项相结合的问题来讨论一下。 工具按钮和菜单项相结合就是指当选择工具按钮或菜单命令时,操作结果是一样的。实现的具体方法是在工具按钮的属性对话框中将按钮的ID号设置为相关联的菜单项ID。例如,下面的示例过程。 [例Ex_TM] 工具按钮和菜单项相结合 (1)创建一个默认的单文档应用程序Ex_TM。 (2)在项目工作区窗口中选择ResourceView页面,展开结点,双击资源“Menu”项中的IDR_MAINFRAME,利用菜单编辑器在“编辑”菜单的子菜单最后添加一个水平分隔符和一个“测试(&T)”菜单项(ID_EDIT_TEST)。 为以后叙述方便,凡“XXXX”菜单项(ID_YY_YY)表示的菜单项是通过该菜单项属性对话框来设置的,且标题为XXXX,ID号为ID_YY_YY。本书作此约定!
34
4.2工具栏 (3)双击资源“Toolbar”项中的IDR_MAINFRAME,打开工具栏资源编辑器,为其添加并设计一个按钮,其位置和内容如图4.19所示。 图4.19 设计的工具栏按钮
35
4.2工具栏 (4)双击刚才设计的第一个工具按钮,弹出该工具按钮的属性对话框,将该工具按钮的ID号设为ID_EDIT_TEST,在提示框内键入“工具栏按钮和菜单项命令相结合。\n测试”。 (5)编译运行并测试。当程序运行后,将鼠标移至刚才设计的工具按钮处,这时在状态栏上显示出“工具栏按钮和菜单项命令相结合。”信息,若稍等片刻后,还会弹出提示小窗口,显示出“测试”字样。但此时的“测试”按钮和“测试”菜单项都是灰显,暂时无法操作它,这是因为程序中还没有任何与ID_EDIT_TEST命令相映射的消息处理函数。 (6)用MFC ClassWizard在CMainFrame添加ID_EDIT_TEST的COMMAND消息映射,保留默认的消息处理函数名,添加下列代码: void CMainFrame::OnEditTest() { MessageBox("测试命令已执行!"); } (7)再次编译运行并测试。
36
4.2工具栏 多个工具栏的使用 在用MFC AppWizard创建的文档应用程序中往往只有一个工具栏,但在实际应用中,常常需要多个工具栏。这里以一个实例的形式来讨论多个工具栏的创建、显示和隐藏等操作。 [例Ex_MultiBar] 多个工具栏的使用 (1)创建一个默认的单文档应用程序Ex_MultiBar。 (2)将项目工作区切换到ResourceView页面,展开“Toolbar”(工具栏)资源,用鼠标单击IDR_MAINFRAME不松开,然后按下Ctrl键,移动鼠标将IDR_MAINFRAME拖到 Toolbar资源名称上,这样就复制了工具栏默认资源IDR_MAINFRAME,复制后的资源 标识系统自动设为IDR_MAINFRAME1。 (3)右击工具栏资源IDR_MAINFRAME1,从弹出的快捷菜单中选择Properties命令,如图4.20所示,将ID设为IDR_TOOLBAR1。 图4.20 工具栏属性对话框
37
4.2工具栏 (4)双击IDR_TOOLBAR1,打开工具栏资源,删除几个“编辑”相关的工具按钮(目的是让IDR_TOOLBAR1工具栏与IDR_MAINFRAME有明显区别)。 (5)在CMainFrame类中添加一个成员变量m_wndTestBar,变量类型为CToolBar(CToolBar类封装了工具栏的操作)。 protected: // control bar embedded members CStatusBar m_wndStatusBar; CToolBar m_wndToolBar; CToolBar m_wndTestBar; (6)在CMainFrame::OnCreate函数中添加下面的工具栏创建代码: 分析和说明: ● 代码中,CreateEx是CToolBar类的成员函数,用来创建一个工具栏对象。该函数的第1个参数是用来指定工具栏所在的父窗口指针,this表示当前的CMainFrame类窗口指针。第2个参数用来指针工具按钮的风格,当为TBSTYLE_FLAT时表示工具按钮是“平面”的。第3个参数用来指针工具栏的风格。由于这里的工具栏是CMainFrame的子窗口,因此需要指定WS_CHILD | WS_VISIBLE。CBRS_TOP表示工具栏放置在父窗口的顶部,CBRS_GRIPPER表示工具栏前面有一个“把手”,CBRS_TOOLTIPS表示允许有工具提示,CBRS_FLYBY表示在状态栏显示工具提示文本,CBRS_SIZE_DYNAMIC表示工具栏在浮动时,其大小是可以动态改变的。第4个参数是用来指定工具栏四周的边框大小,一般都为0。最后一个参数是用来指定工具栏这个子窗口的标识ID(与工具栏资源标识不同)。
38
4.2工具栏 ● if语句中的LoadToolBar函数是用来装载工具栏资源。若CreateEx或LoadToolBar的返回值为0,即调用不成功,则显示诊断信息“Failed to create toolbar”。TRACE0是一个用于程序调试的跟踪宏。OnCreate函数返回-1时,主框架窗口被清除。 ● 应用程序中的工具栏一般具有停靠或浮动特性,m_wndTestBar.EnableDocking使得m_wndTestBar对象可以停靠,CBRS_ALIGN_ANY表示可以停靠在窗口的任一边。EnableDocking(CBRS_ALIGN_ANY)是调用的是CFrameWnd类的成员函数,用来让工具栏或其他控制条在主框架窗口可以进行停靠操作。DockControlBar也是CFrameWnd类的成员函数,用来将指定的工具栏或其他控制条进行停靠。 ● AFX_IDW_TOOLBAR是系统内部的工具栏子窗口标识,并将AFX_IDW_TOOLBAR + 1的值表示默认的状态栏子窗口标识。如果在创建新的工具栏时没有指定相应的子窗口标识,则会使用默认的AFX_IDW_TOOLBAR。这样,当打开“查看”菜单时,单击“工具栏”菜单时,显示或隐藏的工具栏不是原来的工具栏而是新添加的工具栏。因此,需要重新指定工具栏子窗口的标识,并使其值等于AFX_IDW_TOOLBAR + 10。
39
4.2工具栏 (7)编译运行,结果如图4.21所示。 图4.21 多个工具栏第一次运行的结果 (8)事实上多个工具栏的程序重点不是在于工具栏的显示,而是如何控制其显示。CFrameWnd::ShowControlBar函数就是起到这样的作用,有3个参数,第1个参数用来指定要操作的工具栏或状态栏指针,第2个参数是一个布尔型,当为TRUE时表示显示,否则表示隐藏,第3个参数用来表示是否延迟显示或隐藏,当为FALSE时表示立即显示或隐藏。
40
4.2工具栏 (9)打开菜单资源IDR_MAINFRAME,在“查看”菜单下添加一个“新的工具栏(&N)”菜单命令(ID_VIEW_NEWBAR)。 (10)用MFC ClassWizard在CMainFrame添加ID_VIEW_NEWBAR的COMMAND消息映射,保留默认的消息处理函数名,添加下列代码: void CMainFrame::OnViewNewbar() { int bShow = m_wndTestBar.IsWindowVisible(); ShowControlBar( &m_wndTestBar, !bShow, FALSE); } 代码中,IsWindowVisible 函数用来判断窗口(对象)是否可见。若为可见,则下句的ShowControlBar函数调用就使其隐藏,反之就显示。 (11)编译运行并测试。 事实上,当ID_VIEW_NEWBAR工具栏显示时,还应使菜单“新的工具栏(&N)”文本前面能有一个显示,此时需跟踪交互对象的更新消息方可实现,后面会讨论这个问题。
41
4.3状态栏 状态栏的定义 用MFC AppWizard(.exe)创建的SDI或MDI应用程序框架中,有一个静态的indicator数组,它是在MainFrm.cpp文件中指定的,被MFC用作状态栏的定义。 Static UINT indicators[]= { ID_SEPARATOR, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, } 图4.22 indicators数组的定义
42
4.3状态栏 状态栏的常用操作 Visual C++ 6.0中可以方便地对状态栏进行操作,如增减窗格、在状态栏中显示文本、改变状态栏的风格和大小等,并且MFC的CStatusBar类封装了状态栏的大部分操作。 1. 增加和减少窗格 状态栏中的窗格可以分为信息行窗格和指示器窗格两类。若在状态栏中增加一个信息行窗格,则只需在indicators数组中的适当位置中增加一个ID_SEPARATOR标识即可;若在状态栏中增加一个用户指示器窗格,则在indicators数组中的适当位置增加一个在字符串表中定义过的资源ID,其字符串的长度表示用户指示器窗格的大小。若状态栏减少一个窗格,其操作与增加相类似,只需减少indicators数组元素即可。 2. 在状态栏上显示文本 调用CStatusBar::SetPaneText函数可以更新任何窗格(包括信息行窗格)中的文本。此函数原型描述如下: BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE ); 其中,lpszNewText表示要显示的字符串。nIndex是表示设置的窗格索引(第一个窗格的索引为0)。若bUpdate为TRUE,则系统自动更新显示的结果。 3. 示例 本示例用来将鼠标在窗口客户区的位置显示在状态栏上。
43
4.3状态栏 [例Ex_SDIMouse] 在状态栏上显示鼠标位置 (1)创建一个默认的单文档应用程序Ex_SDIMouse。
(2)将项目工作区切换到ClassView页面,展开CMainFrame所有项,双击构造函数CMainFrame,在文档窗口中出现该函数的定义,在它的前面就是状态栏数组的定义。 (3)将状态栏indicators数组的定义改为下列代码: static UINT indicators[] = { ID_SEPARATOR, }; (4)由于鼠标移动消息WM_MOUSEMOVE在CMainFrame类映射后不起作用,因此只能映射到CEx_SDIMouseView类中。但是,这样一来,就需要更多的代码,因为状态栏对象m_wndStatusBar是在CMainFrame类定义的成员变量,因而需要在CEx_SDIMouseView类中添加访问CMainFrame类的代码。CEx_SDIMouseView::OnMouseMove函数代码如下:
44
4.3状态栏 void CEx_SDIMouseView::OnMouseMove(UINT nFlags, CPoint point) {
CString str; CMainFrame* pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd; // 获得主窗口指针 CStatusBar* pStatus=&pFrame->m_wndStatusBar; // 获得主窗口中的状态栏指针 if (pStatus) str.Format("X=%d, Y=%d",point.x, point.y); // 格式化文本 pStatus->SetPaneText(1,str); // 更新第二个窗格的文本 } CView::OnMouseMove(nFlags, point);
45
4.3状态栏 代码中,AfxGetApp是CWinApp类的一个成员函数,该函数可在应用程序项目中的任何类中使用,用来获取应用程序中的CWinApp类对象指针。 (5)将MainFrm.h文件中的受保护变量m_wndStatusBar变成公共变量。 (6)在Ex_SDIMouseView.cpp文件的开始处增加下列语句: #include "Ex_SDIMouseView.h" #include "MainFrm.h" (7)编译并运行,结果如图4.23所示。 图4.23 鼠标的位置显示在状态栏上
46
4.3状态栏 4.33 改变状态栏的风格 在MFC的CStatusBar类中,有两个成员函数可以改变状态栏风格,它们是:
改变状态栏的风格 在MFC的CStatusBar类中,有两个成员函数可以改变状态栏风格,它们是: void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth ); void SetPaneStyle( int nIndex, UINT nStyle ); 其中,参数nIndex表示要设置的状态栏窗格的索引,nID用来为状态栏窗格指定新的ID,cxWidth表示窗格的像素宽度,nStyle表示窗格的风格类型,用来指定窗格的外观,例如SBPS_POPOUT表示窗格是凸起来的,具体见表4.6。 表4.6 状态栏窗格的风格类型 风格类型 含 义 SBPS_NOBORDERS 窗格周围没有3D边框 SBPS_POPOUT 反显边界以使文字“凸出来” SBPS_DISABLED 禁用窗格,不显示文本 SBPS_STRETCH 拉伸窗格,并填充窗格不用的空白空间。但状态栏只能有一个窗格具有这种风格 SBPS_NORMAL 普通风格,它没有“拉伸”,“3D边框”或“凸出来”等特性
47
4.3状态栏 例如,在前面的示例中,将OnMouseMove函数修改为下列代码,则结果如图4.24所示。
void CEx_SDIMouseView::OnMouseMove(UINT nFlags, CPoint point) { CString str; CMainFrame* pFrame=(CMainFrame*)AfxGetApp()->m_pMainWnd; // 获得主窗口指针 CStatusBar* pStatus=&pFrame->m_wndStatusBar; // 获得主窗口中的状态栏指针 if (pStatus) pStatus->SetPaneStyle(1, SBPS_POPOUT); str.Format("X=%d, Y=%d",point.x, point.y); // 格式化文本 pStatus->SetPaneText(1,str); // 更新第二个窗格的文本 } CView::OnMouseMove(nFlags, point); 图4.24 设置状态栏的风格
48
4.4交互对象的动态更新 为了能使用户交互对象动态更新,MFC通过ClassWizard直接映射交互对象的更新命令消息来实现的。它自动将用户交互对象的ID标识符与ON_UPDATE_COMMAND_UI宏相连接并产生处理更新消息的相应函数。 例如,打开Ex_MultiBar应用程序,用MFC ClassWizard在CMainFrame类中添加菜单ID_VIEW_NEWBAR的UPDATE_COMMAND_UI消息映射,保留其默认的映射函数名,并添加下列代码: void CMainFrame::OnUpdateViewNewbar(CCmdUI* pCmdUI) { int bShow = m_wndTestBar.IsWindowVisible(); pCmdUI ->SetCheck( bShow ); } 代码中,OnUpdateViewNewbar是ID_VIEW_NEWBAR的更新命令消息的消息映射函数。该函数只有一个参数,它是指向CCmdUI对象的指针。CCmdUI 类仅用于ON_UPDATE_COMMAND_UI消息映射函数,它的成员函数将对菜单项、工具按钮等用户交互对象起作用,具体如表4.7所示。编译运行并测试。打开“查看”菜单,可以看到“新的工具栏(&N)”菜单前面有一个“”,再次选择“新的工具栏(&N)”菜单,则新创建的工具栏不见,“新的工具栏(&N)”菜单前面没有任何标记。若将代码中的SetCheck改为SetRadio,则“”变成了“”,这就是交互对象的更新效果。
49
4.4交互对象的动态更新 表4.7 CCmdUI 类的成员函数对用户交互对象的作用 用户交互对象 Enable SetCheck
SetRadio SetText 菜单项 允许或禁用 )或未选中 选中用点() 设置菜单文本 工具栏按钮 选定、未选定或不确定 同SetCheck 无效 状态栏窗格(PANE) 使文本可见或不可见 边框外凸或正常 设置窗格文本 CDialogBar中的按钮 选中或未选中 设置按钮文本 CDialogBar中的控件 设置窗口文本 选中(
50
4.5综合应用 为了能对学生成绩记录进行管理,常见的操作包括:添加、删除和查找等。 这些操作在在文档应用程序中实现时可以有多种界面方法,具体如下: (1)构造单独的“操作”工具栏,包含上述操作的工具按钮。 (2)在文档应用程序的主菜单中的“查看”菜单下添加一个“操作工具栏(&O)”菜单命令,用来控制“操作”工具栏的显示。 (3)当“操作”工具栏显示时,“操作工具栏(&O)”菜单命令前面有一个“”,且在 “帮助”菜单项前面出现“操作(&O)”菜单项,包含用于添加、删除和查找的各个子菜单项。 (4)当“操作”工具栏隐藏时,“操作(&O)”菜单项消失,且“操作工具栏(&O)”菜单命令前面没有任何标记。用快捷键Ctrl+1关联“操作工具栏(&O)”菜单命令。 (5)单击操作工具栏的“添加”按钮或选择“操作”菜单中的“添加”命令,弹出“学生成绩”对话框,单击[确定]按钮,学生成绩记录显示在状态栏上。结果如图4.25所示。
51
4.5综合应用 图4.25 综合应用题图 具体实现步骤如下(分四步阐述:设计学生成绩对话框、设计“操作”工具栏和菜单、添加联动代码、修改状态栏完善代码): [例Ex_A4] 第四章综合应用 1)设计学生成绩对话框
52
4.5综合应用 (1)用MFC AppWizard(exe)创建一个默认的单文档应用程序Ex_A4。
(2)添加一个新的对话框资源,通过其属性对话框,将ID标识符改为IDD_SCORE,标题为“学生成绩”,将对话框字体改为“宋体,9号”。 (3)显示对话框网格,调整对话框的大小,向对话框添加如表3.7所示的控件,调整控件的位置,结果如图4.26所示。 图4.26 设计的学生成绩对话框 表4.8 学生成绩对话框添加的控件 添加的控件 ID标识符 标 题 其他属性 编辑框 IDC_EDIT_NAME —— 默认 IDC_EDIT_NO IDC_EDIT_S1 IDC_EDIT_S2 IDC_EDIT_S3
53
4.5综合应用 (4)双击对话框模板空白处,为该对话框模板创建一个对话框类CScoreDlg。
(5)在MFC ClassWizard的Member Variables页面中,确定Class name中是否已选择了CScoreDlg,选中所需的控件ID标识符,双击鼠标或单击Add Variables按钮。依次为表4.9控件增加成员变量。 表4.9 控件变量 控件ID标识符 变量类别 变量类型 变量名 范围和大小 IDC_EDIT_NAME Value CString m_strName 20 IDC_EDIT_NO m_strNO 20 IDC_EDIT_S1 float m_fScore1 0.0 ~ 100.0 IDC_EDIT_S2 m_fScore2 IDC_EDIT_S3 m_fScore3
54
4.5综合应用 2)设计“操作”工具栏和菜单 (1)按Ctrl+R快捷键,在“插入资源”对话框中,选中“Toolbar”类型,单击[新建]按钮,这样就会为应用程序添加一个工具栏资源,并自动打开相应的编辑器,且第一次添加的默认ID为IDR_TOOLBAR1。 (2)按图4.27所示,用工具栏资源编辑器为IDR_TOOLBAR1添加3个按钮,其ID号依次设为:ID_OP_ADD、ID_OP_DEL和ID_OP_SEEK。 图4.27 工具栏设计
55
4.5综合应用 (3)在CMainFrame类中添加一个成员变量m_wndOpBar,变量类型为CToolBar。
(4)在CMainFrame::OnCreate函数中添加下面的工具栏创建代码: (5)按Ctrl+R快捷键,在“插入资源”对话框中,选中“Menu”类型,单击[新建]按钮,这样就会为应用程序添加一个菜单资源,并自动打开相应的编辑器,且第一次添加的默认ID为IDR_MENU1。 (6)添加“操作”顶层下拉菜单,其子菜单依次为“添加(&A)”(ID_OP_ADD)、“删除(&D)”(ID_OP_DEL)和“查找(&K)”(ID_OP_SEEK)。 (7)在ResourceView页面中,双击资源“Menu”中的IDR_MAINFRAME,在“查看”菜单的最后添加一个菜单命令项“操作工具栏(&O)\tCtrl+1”(ID_VIEW_OP)。 3)添加联动代码 (1)在项目工作区窗口的ResourceView(资源视图)页面中,展开中Accelerator(加速键)的资源项,双击IDR_MAINFRAME。在右侧窗口中出现如图4.7的加速键资源列表。 (2)双击加速键列表的最下端的空行,在弹出的“Accel 属性”(Accel Properties)对话框 中 将ID号选为ID_VIEW_OP,然后单击[下一键]按钮,并按下Ctrl+1作为此加速键的键值。关闭“Accel 属性”对话框。
56
4.5综合应用 (3)为CMainFrame类添加一个CMenu类的成员变量m_popMenu,以及一个成员函数UpdateOpMenu,代码如下: (4)按Ctrl+W键打开MFC ClassWizard对话框,切换到Message Maps页面,在CMainFrame类中添加ID_VIEW_OP命令的COMMAND和UPDATE_COMMAND_UI消息映射,保留默认的映射函数名,添加下列代码: void CMainFrame::OnViewOp() { int bShow = m_wndOpBar.IsWindowVisible(); ShowControlBar( &m_wndOpBar, !bShow, FALSE); UpdateOpMenu( !bShow ); } void CMainFrame::OnUpdateViewOp(CCmdUI* pCmdUI) pCmdUI ->SetCheck( bShow );
57
4.5综合应用 (5)修改CMainFrame::OnCreate中的后面代码:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { … m_wndOpBar.SetWindowText("操作"); m_popMenu.LoadMenu( IDR_MENU1 ); UpdateOpMenu( TRUE ); return 0; } (6)编译运行并测试。此时会发现工具栏、菜单项和快捷键的联动功能已实现,但“操作”菜单项和工具按钮都是灰显的,这是因为还没有对这些命令进行映射。 4)修改状态栏,完善代码 (1)将项目工作区切换到ClassView页面,展开CMainFrame所有项,双击构造函数CMainFrame,在文档窗口中出现该函数的定义,在它的前面就是状态栏数组的定义。 (2)将状态栏indicators数组的定义改为下列代码: static UINT indicators[] = ID_SEPARATOR, };
58
4.5综合应用 (3)按Ctrl+W键打开MFC ClassWizard对话框,切换到Message Maps页面,在CMainFrame类中分别添加ID_OP_ADD、ID_OP_DEL和ID_OP_SEEK 的COMMAND消息映射,保留默认的映射函数名。其中,仅对OnOpAdd添加下列代码(因为其他2个命令的映射仅使相应的菜单命令项和工具按钮有效): void CMainFrame::OnOpAdd() { CScoreDlg dlg; if (IDOK == dlg.DoModal() ) // 当输入记录有效后,获取相关记录,然后在状态上显示出来 CString strRes; strRes.Format( "姓名:%s, 学号:%s, 3门成绩: %.1f, %0.1f, %.1f", dlg.m_strName, dlg.m_strNO, dlg.m_fScore1, dlg.m_fScore2, dlg.m_fScore3 ); m_wndStatusBar.SetPaneText( 1, strRes ); } else m_wndStatusBar.SetPaneText( 1, "无有效的学生成绩记录!" ); }
59
4.5综合应用 (4)在MainFrm.cpp文件的前面添加CScoreDlg类的头文件包含: #include "MainFrm.h"
#include "ScoreDlg.h" (5)编译运行并测试。 总之,菜单命令(包括快捷菜单)、工具栏上的按钮以及快捷键是构成文档应用程序框架的最基本命令系统,而提示文本、交互对象更新状态和状态栏等是构成文档应用程序框架的最基本信息系统,这些都是属于文档应用程序框架的界面问题。事实上,文档应用程序的界面设计中还有图标、光标以及框架窗口的样式设置等一系列问题,下一章就来讨论。
60
习题 1. 菜单有哪些常见的规则?什么是助记符?它是如何在菜单中定义的? 2. 菜单项的消息有哪些?若对同一个菜单用ClassWizard分别在视图类和主框架窗口类CMainFrame都处理其COMMAND消息,并在它们的函数中添加相同的代码,则当用户选择该菜单后,会有什么样的结果?为什么? 3. 如何对已有的菜单中添加一个菜单项、一个弹出菜单? 4. 什么是键盘快捷键?它是如何定义的? 5. 什么是快捷菜单?快捷菜单能否通过映射右击鼠标消息来实现,为什么? 6. 如何使一个工具按钮和某菜单项命令相结合?如何显示和隐藏一个工具栏?如何设置工具栏的标题? 7. 状态栏的作用是什么?状态栏的窗格分为几类?如何添加和减少相应的窗格? 8. 若状态栏只有一个用户定义的指示器窗格(其ID号为ID_TEXT_PANE),应如何定义?若当用户在客户区双击鼠标时,在该窗格中显示“双击鼠标”字样,则应如何编程? 9. 什么是命令更新消息?它的作用是什么?
Similar presentations