摘要
本讲先来用资源编辑器对菜单和工具栏进行可视化设计,然后讨论命令消息的COMMAND和UPDATE_COMMAND_UI消息映射,并说明了工具按钮和菜单命令的联动方法,最后讨论工具栏的显示和隐藏、快捷菜单的实现,以及在状态栏上如何显示指定文本的方法。
目录
菜单的可视化设计及其命令映射
工具栏设计及与菜单命令的联动
工具栏的显示和隐藏的快捷方式实现
在状态栏上显示文本
本讲中常用操作问题的解决方法
结束语
菜单的可视化设计及其命令映射
在上一讲中,我们主要讨论了文档数据的读取和显示,在这里我们先来打开上一讲中的单文档应用程序项目Viewer,然后对其菜单和工具栏进行设计。需要说明的是,Visual C++ .NET把Windows各种应用程序所需要的图形元素,例如菜单、工具栏、对话框、图标、光标等,作为可以装入应用程序的资源来存放。例如,Visual C++.NET将Viewer应用程序的资源都存放在Viewer.rc文件中,这种资源同源代码相分离的机制,能大大方便用户的操作,而且每一个资源元素都用相应的资源ID号来标识。
1. 菜单的可视化设计
在菜单设计之前,我们先了解一下菜单及其设计规范。
菜单可以有多级结构,即一个菜单项可以有多个子菜单,而一个子菜单又可以包含多个下一级的子菜单,依此类推。但在菜单实际设计时,菜单的级数一般以2~3级为宜,而且设计时还要注意一些菜单原则。例如,若单击某菜单项会弹出一对话框,那么在该菜单项文本后加上"…";若菜单项需要助记符(带下划线的字符),则用括号将其括起来,对于顶层菜单项来说,当按住"Alt"键不放,再按助记符所对应的字符键时,对应的顶层菜单就会被打开,若子菜单项还有助记符,则只要按对应的字符键,则可执行该菜单命令;定义助记符时,只要在字符前面加上"&"符号即可;若某项菜单需要快捷键的支持,则一般将其列在相应菜单项文本之后。
下面为Viewer项目添加一个"格式"菜单,其下有两个菜单项。一个是"设置字体"菜单,另一个是"文本颜色"菜单,分别用来改变文本显示的字体和颜色。具体步骤如下:
(1) 将解决方案资源管理器切换到"资源视图",若没有该标签,则打开"视图"菜单,选择"资源视图"菜单命令即可。
(2) 展开资源所有节点,双击Menu下的IDR_MAINFRAME,打开该程序的菜单资源。需要说明的是,凡是标识为IDR_MAINFRAME的资源均是程序框架加装的默认资源。
(3) 如图1所示,单击顶层菜单最右边的"请在此处输入",直接按Insert键或右击"请在此处输入",在弹出的菜单中单击"新插入"。再单击"请在此处输入",该位置就会变成一个可编辑的文本框,出现了插入符。键入菜单文本内容"格式(&M)",然后按Enter键。
(4) 单击"格式(&M)"菜单项下方的"请在此处输入",按Insert键,键入菜单文本内容"设置字体(&F)",然后按Enter键。
(5) 单击"设置字体(&F)",在右下角的属性窗口中就会列出其所有的属性,如图2所示。需要说明的是,在属性窗口中,我们可以重新编辑菜单的文本内容和资源标识ID。Caption(标题)属性是用来标识菜单项显示文本,如果使用助记符,则字母的前面须有一个&符号;当Popup(弹出)属性为True时表示该菜单项是一个弹出式菜单,即该菜单下还有多个子菜单,此时属性ID、Separator和Prompt项无效;因此,若添加的是一个可以映射的菜单命令,则Popup属性一定要设为False。当Separator(分隔符)属性为True时表示菜单项是一个分隔符或是一条水平线;而Prompt(提示)属性用来指明鼠标指针移至该菜单项时在状态栏上显示的提示信息。
(6) 将默认的菜单项"设置字体(&F)"标识ID_130改为ID_FOMAT_TXTFONT。更改时直接在属性窗口中的ID栏右侧的框中进行编辑,修改后按Enter键。
(7) 重复上述步骤,在"格式"菜单下再添加一个菜单项"文本颜色(&C)",ID为ID_FORMAT_TXTCOLOR,结果如图3所示。
(8) 单击"格式"菜单不松开,然后将其拖放到"视图"和"帮助"之间。
2. 菜单的命令映射
此时运行程序,则"格式"菜单下的命令都是"灰显"(即显示的颜色是灰色的)的,我们无法选择相应的菜单命令,这是因为我们还没有对菜单的命令消息进行映射。下面就来进行映射,由于我们添加的这些菜单命令是想更改变文本内容显示的字体,因此我们将菜单命令的映射添加到视图类CViewerView中,如下面的过程:
(1) 将解决方案资源管理器切换到"类视图",展开节点,选定"CViewerView",在其属性窗口中,单击"事件"按钮,结果如图4所示。
(2) 找到前面添加的菜单项ID_FORMAT_TXTFONT,单击该ID前面的"+",展开后出现可以映射的消息,由于菜单消息是命令消息,因为我们在COMMAND消息框的右侧,单击后选择"<添加>OnFormatTxtfont",如图4所示。这样相应的映射就被添加到CViewerView类中,此时文档窗口中自动定位到该函数的实现代码处。
(3) 重复上一步为菜单项ID_FORMAT_TXTCOLOR添加COMMAND消息映射。
注意:同一命令消息的响应是根据对象的级别来决定的,对于单文档应用程序来说,各对象的级别从高到低依次为视图类(文档窗口)、文档类、主框架窗口类、应用程序类。
3. 完善"格式"菜单代码
(1) 为CViewerView类添加两个成员变量(添加成员变量的方法上一讲已讨论过),一个是LOGFONT类型的m_lfTextFont,另一个是COLORREF类型的m_crTxtColor。LOGFONT是逻辑字体类型,所谓"逻辑字体",它是应用程序对于理想字体的一种描述方式。在使用逻辑字体绘制文字时,系统会采用一种特定的算法把逻辑字体映射为最匹配的物理字体(实际安装在操作系统中的字体)。而COLORREF是专门用来定义RGB颜色的数据类型,RGB颜色是通过红(R)、绿(G)、蓝(B)三种基色分量的不同值混合而成的。
(2) 在构造函数CViewerView::CViewerView()中添加上述两个成员变量的初始化代码,如图5所示。
(3) 在CViewerView::OnFormatTxtfont()函数中添加如图6所示的代码。
CFontDialog类为我们提供了字体及其文本颜色选择的通用对话框,在构造对象中指定m_lfTextFont指针,其目的是用来设置对话框显示的逻辑字体,这样当下一次显示字体对话框时,就会显示当前的字体特性。
(4) 在CViewerView::OnFormatTxtcolor()函数中添加如图7所示的代码。
CColorDialog类封装了通用颜色对话框的全部操作。在定义对话框对象时,可以指定默认选定的颜色值,若不指定,则默认颜色值为RGB(0,0,0)(黑色)。
(5) 修改CViewerView::OnDraw()函数代码,如图8所示的加框部分。
(6) 运行程序,打开当前目录中的ReadMe.txt文档,打开"格式"菜单,选中相应的菜单命令,改变其字体和颜色。图9是其中的一个结果。
工具栏设计及与菜单命令的联动
工具栏上通常有一系列的工具按钮,所有的按钮图像都具有相同的尺寸,一般是15像素高,16像素宽,借助它们可以提高用户的工作效率,并且将常用的菜单命令也放在工具栏上,它们实际是命令不同的用户方式。
1. 添加并设计工具栏
(1) 将解决方案资源管理器窗口切换到"资源视图",展开后右击Toolbar,在弹出的快捷菜单中单击"插入Toolbar"。这样,一个工具栏资源就添加到项目中,默认的标识为IDR_TOOLBAR1。
(2) 添加并设计2个工具按钮,结果如图10所示。
图11 工具按钮的设计
由于其编辑操作与Windows的画图相类似,故这里仅列出操作的一些技巧:
① 单击空白按钮后就可以编辑其图像,同时系统在随后的位置自动添加一个空白按钮。
② 用鼠标可以将一个按钮拖放到工具栏上的其他位置上。若拖动时按下Ctrl键,则复制一个工具按钮。若将工具按钮拖出工具栏,则该工具按钮被删除。
③ 按Delete键可以将当前工具按钮的图像用背景色填充。
④ 在工具按钮之间添加间隔时,可按不同情况来操作。若工具按扭前没有任何间隔,拖动该工具按钮向右直到它覆盖相邻工具按钮的一半以上后,释放鼠标键,则此工具按钮前出现间隔。若工具按钮前面有间隔而后面没有间隔,拖动该工具按钮向左直到它的左边界接触到它前面的工具按钮为止,释放鼠标键,则此工具按钮后面将出现间隔。
⑤ 若工具按钮前后均有间隔,拖动该工具按钮向右直到它接触相邻工具按钮,则此工具按钮前的间隔保留,工具按钮后的间隔消失。反之,若拖动该工具按钮向左直到它接触相邻的前一个工具按钮,则此工具按钮前面的间隔消失,后面的间隔仍保留。
⑥ 删除工具按钮间隔时,只要将间隔一端的工具按钮拖向间隔另一端的工具按钮,直到与另一个按钮重叠一半以上即可。
(3) 单击第一个工具按钮,在工具按钮的属性窗口中,将其ID号选择为ID_FORMAT_TXTFONT,这是将工具按钮与菜单命令联动的关键。将其Prompt属性内容改成"改变显示的字体\n字体"。Prompt属性是用来指定工具按钮的提示文本。例如若为"改变显示的字体\n字体"时,则表示当鼠标移至该工具按钮时,在状态栏中就会显示"改变显示的字体",稍等片刻后还会弹出一个小的提示窗口,显示出"字体"字样。注意:提示窗口显示的内容是Prompt属性字符串中"\n"后的内容。
(4) 将第二个工具按钮的ID号选择为ID_FORMAT_TXTCOLOR,Prompt设为"改变文本的显示颜色\n颜色"。
2. 工具栏代码的实现
(1) 在CMainFrame类中添加一个成员变量m_wndFormatBar,变量类型为CToolBar。CToolBar类封装了工具栏的操作。
(2) 在CMainFrame::OnCreate()函数中添加工具栏的创建代码,如图11所示的加框部分。
程序说明:
① 主框架类CMainFrame用来负责窗口的菜单栏、工具栏和状态栏的创建和更新工作。因此我们将工具栏的创建代码添加在CMainFrame的OnCreate()函数中。
② CreateEx()是CToolBar类的成员函数,用来创建一个工具栏对象。
③ if语句的LoadToolBar()函数是用来装载工具栏资源。若CreateEx()或LoadToolBar()的返回值为0,即调用不成功,则显示诊断信息"未能创建工具栏"。TRACE0是一个用于程序调试的跟踪宏。OnCreate()函数返回-1时,主框架窗口被清除。
④ 应用程序中的工具栏一般具有停靠或浮动特性,m_wndFormatBar.EnableDocking()使得m_wndFormatBar对象可以停靠,CBRS_ALIGN_ANY表示可以停靠在窗口的任一边。 EnableDocking(CBRS_ALIGN_ANY)是调用的是CFrameWnd类的成员函数,用来让工具栏或其他控制条在主框架窗口可以进行停靠操作。DockControlBar()也是CFrameWnd类的成员函数,用来将指定的工具栏或其他控制条进行停靠。
⑤ AFX_IDW_TOOLBAR是系统内部的工具栏子窗口标识,并将AFX_IDW_TOOLBAR+1的值表示默认的状态栏子窗口标识。如果在创建新的工具栏时没有指定相应的子窗口标识,则会使用默认的AFX_IDW_TOOLBAR。这样,当打开"视图"菜单时,单击"工具栏"菜单时,显示或隐藏的工具栏不是原来的工具栏而是新添加的工具栏。因此,我们需要重新指定工具栏子窗口的标识,并使其值等于AFX_IDW_TOOLBAR + 10。
(3) 运行程序,可以看到新添加的工具栏,如图12所示,左图是工具栏开始的停靠情况,右图是工具栏浮动的情形。
需要说明的是,上述工具按钮是与菜单命令联动,因此无需进行工具按钮命令的消息映射,因为该命令已在菜单操作该命令已映射过。若是单独一个工具按钮,则需要对该工具按钮进行命令消息的映射,否则按钮是灰显的。工具按钮的命令消息映射方法与菜单命令相同。
工具栏的显示和隐藏的快捷方式实现
在图12中,关闭浮动的"格式"工具栏后,若再显示该工具栏则无法进行,为此我们需要添加相关的控制代码。这里我们先来介绍菜单命令的控制方式,然后再说明其他的快捷方式。
1. 菜单命令方式
所谓菜单命令方式,即使用菜单命令来显示和隐藏指定工具栏。需要解决的问题有两个,一是显示和隐藏指定工具栏的函数是什么?二是如何实现菜单项前面的显示状态的更新。所谓显示状态,即当工具栏显示时,该菜单项前面有一个"a",否则什么都没有。
对于第一个问题,我们可以使用CFrameWnd类的成员函数ShowControlBar()来进行,它的原型如下:
void ShowControlBar( CControlBar* pBar, BOOL bShow, BOOL bDelay );
其中,pBar用来指定要操作的控制条指针,bShow为TRUE时表示显示,否则表示隐藏,bDelay表示是否延迟显示或隐藏,当为FALSE时表示立即显示或隐藏。
对于第二个问题,可以通过映射宏ON_UPDATE_COMMAND_UI来实现菜单项和工具栏按钮状态的改变。下面就来实现。
(1) 在"视图"菜单中添加一个菜单项"格式工具栏(&F)",ID为ID_VIEW_FORMAT。如图13所示。
(2) 在CMainFrame类中添加一个成员变量m_bViewFormat,变量类型为BOOL。该变量用来决定新添加的"格式"工具栏是否显示。
(3) 在CMainFrame类的构造函数处,将m_bViewFormat的初值由原来的FALSE改为TRUE。
(4) 在CMainFrame类中分别添加菜单项ID_VIEW_FORMAT的COMMAND和UPDATE_COMMAND_UI事件映射,并在映射函数添加如图14所示的代码。
程序说明:
① CCmdUI类是专门用于交互对象的更新操作,其成员函数Enable()用来使交互对象有效(参数为TRUE)或无效(参数为FALSE),若不指定参数,使用默认的参数值TRUE。
② CCmdUI::SetCheck()用来设置交互对象状态是"选中"(参数为TRUE)还是"未选中"(参数为FALSE)。当"选中"时,SetCheck()在菜单项文本前面加上"a"。
(5) 运行程序。
2. 快捷键方式
快捷键用于那些反复使用的菜单命令或工具按钮命令,当用户执行命令时只要接相应的快捷键即可。下面来添加并使用快捷键。
(1) 将解决方案资源管理器窗口切换到"资源视图",展开Aclearcase/" target="_blank" >ccelerator,双击IDR_MAINFRAME,出现如图15所示的快捷键资源内容。
需要说明的是,在Visual C ++ .NET中,每一个快捷键除了ID外,还有三个属性:修饰符、键和类型。"修饰符"属性用来设置的快捷键是与Alt、Ctrl和Shift的哪一个或几个控制键组合。"键"属性用来设置使用的键。"类型"属性是用来确定键是解释为虚拟键(VIRTKEY)还是解释为ASCII/ANSI。
(2) 单击最下端的空白方框,出现默认的快捷键资源,如图16所示。
(3) 单击ID_ACCELERATOR32776后,该ID字段变成了一个组合框。在这里,我们既可以自己定义一个资源标识,也可以单击右侧的下拉按钮,从中选择一个已有的资源标识。一旦指定了标识,快捷键就与该标识关联起来,这样当按快捷键时就会执行与标识相对应的命令。我们选择前面的菜单标识ID_VIEW_FORMAT。
(4) 单击Ctrl,从中选择可以使用的控制键,单击"键"字段可以选择相应的虚拟键,或直接输入字符,表示相应的字符键。按图17来设置。
(5) 程序运行后,先按住Ctrl,然后再按1键,就可以显示或隐藏格式工具栏了。
需要说明的是,为了使用户能看到各菜单项所对应的快捷键,我们应该在各菜单项的文本后加上快捷键的内容。例如,在将菜单项ID_VIEW_FORMAT的文本内容改成"格式工具栏(&F)\t Ctrl+1",其中的"Ctrl+1"表示该菜单项的快捷键,"\t"用来将其后面的内容在下一个水平制表位置中显示。
3. 快捷菜单方式
工具栏的显示和隐藏的快捷方式最常用的是使用快捷菜单。所谓快捷菜单,它是一种浮动的弹出式菜单,当用户右击鼠标时,就会相应地弹出一个浮动菜单,其中提供了几个与当前选择内容相关的菜单命令。
(1) 在CMainFrame类的属性窗口中,单击"消息"按钮,在列表框中找到并添加WM_CONTEXTMENU消息的映射。如图18所示。
(2) 在映射函数OnContextMenu()中添加代码,如图19所示的加框部分。
需要说明的是:
① 在MFC中,AFX_IDW_DOCKBAR_TOP和AFX_IDW_DOCKBAR_FLOAT之间的值用来标识工具栏的停靠和浮动的窗口,而AFX_IDW_PANE_FIRST是标识第一个视图窗口,由于单文档的视图只有一个,因此它的标识就是该值。
② GetMenu是用来获取指定菜单下的弹出子菜单,参数的值表示子菜单在主菜单中的位置序号,0时表示第1个子菜单,1时表示第2个子菜单,以此类推。
③ TrackPopupMenu()用来弹出一个快捷菜单,第一个参数用来表示菜单在屏幕显示的位置以及鼠标按钮标志,当为TPM_LEFTALIGN时表示菜单的左边位置由第二个参数确定,TPM_RIGHTBUTTON表示用户单击鼠标右键时弹出菜单,最后一个参数表示弹出菜单的父窗口。this是当前对象指针,每个类对象均有这个指针。
(3) 运行程序。图20是两次不同位置右击时弹出的快捷菜单。
在状态栏上显示文本
状态栏是一个水平长条,位于应用程序主窗口的底部。它可以分割成几个窗格,用来显示多组信息。 在"MFC应用程序向导"创建的单文档或多文档应用程序中,MainFrm.cpp文件定义了一个静态的indicator数组,这个数组中的元素与状态栏的窗格一一对应。
默认时,indicator数组元素只有四个:ID_SEPARATOR、ID_INDICATOR_CAPS 、ID_INDICATOR_NUM和ID_INDICATOR_SCRL。其中ID_SEPARATOR用作消息行窗格,用来显示菜单项或工具按钮的提示信息,其余三个元素是用作状态指示器窗格,分别用于
[/p] 、[p align=center][/p] 和 [p align=center>
这三个键的状态显示。
下面的过程用来将字体名和文本颜色值分别显示在状态栏窗格上。
1. 添加状态栏窗格
(1) 将解决方案资源管理器窗口切换到"资源视图",展开后右击Viewer.rc,在弹出的快捷菜单中单击"资源符号"。在"资源符号"对话框中,单击"新建"按钮,添加一个新的ID号ID_STAT_TXTFONT,并取其默认的值101,如图21所示。
图21 添加新的资源符号
(2) 再添加一个新的资源符号ID_STAT_TXTCOLOR,取其默认的值(102)。
(3) 展开"资源视图"中的"String Table"节点,双击"String Table",打开"字符串表"资源。单击最下方的空白框,出现默认的字符串标识和值,单击该字符串标识,在其右侧出现相应的下拉按钮,单击该按钮,从中选择标识ID_STAT_TXTFONT,单击右侧的标题框,输入"显示字体",结果如图22所示。
(4) 同样的方法再为ID_STAT_TXTCOLOR添加新的字符串"当前文本颜色",注意字符串的长度确定了添加的状态栏窗格的大小。
(5) 打开MainFrm.cpp文件,向indicators数组添加两个元素,如图23所示的加框部分。
(6) 运行程序,结果如图24所示,其中显示的文本是在前面设置的字符串,显然不能满足我们的要示。我们的目的是将当前文本显示的字体和当前颜色值在这两个窗格中显示出来。
2. 更新状态栏窗格
更新状态栏的窗格是通过映射窗格ID的更新命令事件UPDATE_COMMAND_UI来实现的,但由于在类的属性窗口中不能直接对窗格ID进行事件映射,因此我们需要另寻他法。除了手动添加外,我们还可以使用临时菜单的办法,如下面的过程。
(1) 打开菜单资源,在"格式"菜单中再添加两个菜单项"1"和"2",分别将其ID号设置为ID_STAT_TXTFONT和ID_STAT_TXTCOLOR。
(2) 由于显示的内容与CViewerView类的成员变量直接有关,因此我们在CViewerView类中分别添加菜单项ID_STAT_TXTFONT和ID_STAT_TXTCOLOR的UPDATE_COMMAND_UI事件映射。
(3) 在映射函数中添加如图22所示的代码。
(4) 打开"生成"菜单,单击"重新生成解决方案"。
(5) 删除刚才在"格式"菜单中添加的"1"和"2"菜单项。
(6) 运行程序,结果如图23所示。
图23 最后运行结果
本讲中常用操作问题的解决方法
由于Visual C++ .NET的本身原因以及人为的操作不当,导致一些问题的出现,下面就来说明。
问题1:添加的菜单项、是工具按钮的标识的名称可以在属性窗口中修改,但其值却始终为0。
解决办法:在属性窗口中,将标识的名称后面添上"=200"(输入时不加引号),然后按Enter键,这时该标识的值就是200。当然,也可指定其他的值。注意,每一个标识符的值要各不相同。
问题2:在属性窗口的"事件"映射页面中,没有找到要映射的已添加的命令标识,但却发现有的标识不是以符号出现的,而是双数字出现的。
虽然这个问题不是大问题,但是看起来有点别扭。解决的步骤如下:
(1) 打开解决方案资源管理器页面,展开所有节点,右击"资源文件"下的"Viewer.rc",从弹出的快捷菜单中选择"打开方式"。
(2) 在弹出的对话框中选择"源代码(文本)编辑器",如图24所示。
(3) 单击"打开"按钮,出现资源的文本内容,如图25所示的片断。
(4) 找到相应的命令标识,然后将数值改为原来的标识符即可。
问题3:添加的代码也对,就是编译时出现类似"无法打开xxx资源"的错误。
解决办法:打开"生成"菜单,选择"重新生成解决方案",再运行程序,一般这种问题都会解决。
问题4:在运行程序中出现"Viewer fatal error LNK1168: 无法打开 Debug/Viewer.exe 进行写入"编译错误。
解决办法:关闭已执行的应用程序Viewer.exe,然后再编译运行程序。
结束语
在本讲中,我们重点讨论了菜单命令和工具按钮的添加和消息映射,工具栏显示和隐藏的各种快捷方式,以及状态栏的文本显示等内容。在下一讲中,我们将重点进行对话框的界面设计、模式和无模式对话框的创建以及DDV/DDX机制的使用等。