1、第三章:工具条和状态栏在上一章中,已经学到了一些基本的界面设计技术。这一课将指导大家如何设计实现工具条和状态栏,并进一步加深对消息驱动机制的理解。 图 4.1 工具条和状态栏 如图 4.1 所示,工具条和状态栏是一个完善的Windows 应用程序的重要组成部分(但不是必需的部分)。工具条一般位于主框架窗口的上部,上面有一些图形按钮。当用户用鼠标在某一按钮上单击时,程序就会执行相应的命令;当鼠标在按钮上停留片刻后,就会弹出一个黄色小窗口并显示工具提示。按钮的图形是它所代表功能的形象表示,人们对于形象图形的辨别速度要快于抽象文字,因此工具条提供了一种比菜单更快捷的用户接口。在一个标准的 Windo
2、ws 应用程序中,工具条的大部分按钮执行的命令与菜单命令相同,这样做的目的是能同时提供形象和抽象的用户接口,以方便用户的使用。状态栏位于主框架窗口的底部,主要用来显示一些提示信息,可细分为几个窗格。状态栏的主要功能是简要解释被选中菜单命令或工具条按钮命令,并显示 SCROLL LOCK、NUM LOCK 等键的状态。这一章包括以下几个部分: 4.1 工具条的可视化设计 从 4.0 版开始,Visual C+支持一种新型的工具条资源,这使得工具条的创建比以往更加方便灵活了。在 MFC 中,工具条的功能由类 CToolBar 实现。工具条资源和工具条类 CToolBar 是工具条的两个要素。创建工
3、具条的基本步骤是:1.创建工具条资源。2.构建一个 CToolBar 对象。3.调用 CToolBar:Create 函数创建工具条窗口。4.调用 CToolBar:LoadToolBar 载入工具条资源。使用缺省配置时,AppWizard 会自动创建一个工具条。如图 4.1 所示,这个工具条包含一些常用按钮,如打开文件、存盘、打印等等。用户可以修改这个工具条,去掉无用的按钮,加入自己需要的按钮。如果用户需要创建两个以上的工具条,则不能完全依赖AppWizard,需要自己手工创建之。本节将分别讨论这两种方法。4.1.1 利用 AppWizard 自动创建自动创建工具条很简单,请读者按以下步骤操
4、作:1、选择 File-New 命令。2、在弹出的标签式对话框中选 Projects 页,然后在该页中选中 MFC AppWizard (exe)项,并在 Project name 一栏中输入 Record 以创建一个名为 Record 的工程。按回车或用鼠标点击 Create 按钮后就进入了 MFC AppWizard 对话框。3、在 MFC AppWizard 对话框的第一步中选中 Single document。这样就会创建一个单文档应用程序,若选择 Multiple documents 项,则将创建一个多文档应用程序。单文档程序一次只能打开一个窗口,显示一个文档的内容,而多文档程序一次
5、可以打开多个窗口,显示多个文档的内容。4、用鼠标点击 Finish 按钮,并在接着的对话框中按OK 按钮。完成以上操作后,工程 Record 被创建并被自动载入 Developer Studio 中。将项目工作区切换到资源视图,并展开资源,就会发现其中有一个名为IDR_MAINFRAME 的 Toolbar(工具条)资源。用鼠标双击“IDR_MAINFRAME”,Developer Studio 会打开一个功能强大的工具条资源编辑窗口,如图 4.2 所示。该窗口的上部显示出了工具条上的按钮,当用户用鼠标选择某一按钮时,在窗口的下部会显示该按钮的位图。在窗口旁边有一个绘图工具面板和一个颜色面板,
6、供用户编辑按钮位图时使用。 图 4.2 工具条资源编辑窗口 提示:如果读者看不到这两个面板,请在Developer Studio 的工具条的空白处单击鼠标右键,并在随之弹出的菜单中选中 Graphics 和 Colors 两项。在修改工具条以前,首先要修改菜单资源。请按以下几步修改菜单资源:1、将项目工作区切换至资源视图,选择并打开menu(菜单)资源类型,双击名为 IDR_MAINFRAME 的菜单资源。2、删除 Edit 菜单。3、删除 File 菜单中除 Exit 以外的所有菜单项。4、在 File 菜单后插入一个名为由此可见 m_wndToolBar 是一个 CToolBar 对象,它
7、是CMainFrame 的成员。现在请紧接着该成员加入一个新的成员:CToolBar m_wndToolBar1;m_wndToolBar1 代表第二个工具条。读者不要以为给 CMainFrame 加入一个 CToolBar 对象就完事了。实际的创建工具条的工作不会在构造 CToolBar 对象时完成,只有调用了类 CToolBar 的一些成员函数后,创建工作才能结束。对工具条的实际创建工作在CMainFrame:OnCreate 函数中完成。OnCreate 函数是在创建窗口时被调用的,这时窗口的创建已部分完成,窗口对象的 m_hWnd 成员中存放的 HWND 句柄也已有效,但窗口还是不可见
8、的。因此一般在 OnCreate 函数中作一些诸如创建子窗口的初始化工作。提示:初学者一个易犯的错误是在构造函数而不是在OnCreate 中创建子窗口。在构造函数中,父窗口并没有创建,如果在这时创建子窗口,则将会因为得不到父窗口的有效 HWND 句柄而导致创建失败。 找到 CMainFrame:OnCreate 函数,对该函数进行一些修改,修改的部分如清单 4.1 的黑体字所示。在以后,凡是程序中手工修改的部分,一般都会用黑体显示。清单 4.1 修改后的 CMainFrame:OnCreate 函数int CMainFrame:OnCreate(LPCREATESTRUCT lpCreateS
9、truct)if (CFrameWnd:OnCreate(lpCreateStruct) = -1)return -1;if (!m_wndToolBar.Create(this) |!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)TRACE0(“Failed to create toolbarn“);return -1; / fail to createif (!m_wndToolBar1.Create(this) |!m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1)TRACE0(“Failed to create toolbar
10、n“);return -1; / fail to createif (!m_wndStatusBar.Create(this) |!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)TRACE0(“Failed to create status barn“);return -1; / fail to create/ TODO: Remove this if you dont want tool tips or a resizeable toolbarm_wndToolBar.SetBarStyle(m_
11、wndToolBar.GetBarStyle() |CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);m_wndToolBar1.SetBarStyle(m_wndToolBar1.GetBarStyle()|CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);/ TODO: Delete these three lines if you dont want the toolbar to/ be dockablem_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);m_wn
12、dToolBar1.EnableDocking(CBRS_ALIGN_ANY);EnableDocking(CBRS_ALIGN_ANY);DockControlBar(DockControlBar(return 0;注意在 CMainFrame:OnCreate 函数首先调用了CFrameWnd:OnCreate。这行代码是 AppWizard 自动加入的,但有必要解释一下。CMainFrame 是 CFrameWnd类的继承类,在 CMainFrame:OnCreate 中首先要调用基类 CFrameWnd 的 OnCreate 函数,因为基类也要进行一些初始化工作,而基类的 OnCrea
13、te 函数不会自动调用,因此需要在继承类的 OnCreate 函数中显式调用。OnCreate 实际上是 WM_CREATE 消息的消息处理函数,读者可能要问,为什么是派生类的 OnCreate 处理WM_CREATE 消息,而不是基类的 OnCreate 呢。如果读者仔细观察 OnCreate 函数在 CMainFrame 类头文件中的说明,就会发现在该函数前有一个 afx_msg 前缀。afx_msg 的作用与 virtual 关键字类似,它使得被说明的函数有虚拟函数的特性,即由继承类而不是基类的处理函数来处理消息。读者可以看出黑体的代码是仿照第一个工具条的创建代码进行编写的。事实上,笔者
14、不过是将原来的代码复制一份,然后在略作修改而已。提示: 读者不必为这种“抄袭”行为感到羞愧。由于Visual C+博大精深,各种类和函数成百上千,除非你有外星人一般的记忆力,否则是不可能记住所有东西的。用 Visual C+编程,重要的是理解而不是记忆。只要你理解了程序的来龙去脉,就可以最大限度的利用现有的成熟代码,提高程序的开发效率和可靠性。 对第二个工具条的创建代码的解释是:首先,调用 CToolBar:Create 以创建工具条窗口,注意Create 函数的参数是 this 指针,这是因为主框架窗口是工具条的父窗口。接着调用CToolbar:LoadToolBar(IDR_TOOLBAR
15、1)以载入工具条资源。然后调用 CToolBar:SetBarStyle 指定工具条的风格,在调用该函数时先调用CToolBar:GetBarStyle 取得工具条的风格,然后在原有风格的基础上又指定了 CBRS_TOOLTIPS、 CBRS_FLYBY 和 CBRS_SIZE_DYNAMIC 风格,这使得工具条可显示工具提示,并可以动态改变尺寸。接着调用CToolBar:EnableDocking(CBRS_ALIGN_ANY)使工具条是可以停泊的,但还需调用CFrameWnd:EnableDocking(CBRS_ALIGN_ANY),只有这样才能实现可停泊的工具条。最后调用CFrameW
16、nd:DockControlBar 以停泊工具条。编译并运行 Record 看看,现在 Record 程序已经拥有两个工具条了。至此创建工具条的任务已经完成,下面需要对工具条编程,以使其能够发挥执行命令的功能。 4.2 工具条的编程技术 4.2.1 命令处理要使菜单和工具条执行命令,光为它们指定命令ID 是不行的,必须为每个命令 ID 定义命令处理函数。如果不为命令定义命令处理函数或下面将要提到的命令更新处理函数,则框架将自动使该命令对应的菜单项和按钮禁止(灰化),这就是 4.1 节中的工具条按钮和菜单项灰化的原因。利用 ClassWizard 可以很方便地加入命令处理函数,请读者按以下步骤操
17、作:按 Ctrl+W 键进入 ClassWizard。 图 4.6 ClassWizard 对话框 如图 4.6 所示,在 Class name 栏中选择CMainFrame,在 Object IDs 栏中选择ID_RECORD_START,在 Messages 栏中双击 COMMAND 项,则 ClassWizard 会弹出一个对话框询问命令处理函数的名字,使用其提供的函数名即可。按 OK 按钮后,函数 OnRecordStart 就被加入到了 Member functions 栏中。仿照第 2 步,为 ID_RECORD_STOP 定义一个命令处理函数。按 OK 按钮关闭 ClassWiz
18、ard 对话框。这时读者会发现 CMainFrame 类多了两个成员函数,OnRecordStart 和 OnRecordStop。现在要在这两个命令处理函数中插入相应的源代码以实现其功能。当然,这里不会真的实现开始录音和停止录音的功能。我们只是让这两个函数发出一个声音,象征性地表示功能的执行,具体代码如清单4.2 所示。清单 4.2 OnRecordStart 和 OnRecordStop 函数void CMainFrame:OnRecordStart() / TODO: Add your command handler code hereMessageBeep(UINT)(-1);void
19、 CMainFrame:OnRecordStop() / TODO: Add your command handler code hereMessageBeep(UINT)(-1);编译并运行 Record,可以看到 Start 和 Stop 命令已经可以执行了。4.2.2 命令更新虽然 Start 和 Stop 命令可以执行了,但是还有一个不足之处。在没有开始录音之前,Stop 命令应该是禁止的,也即对应的菜单项和按钮应是禁止的,这是因为此时没有必要执行该命令。录音开始后,Stop 命令应该允许,而 Start 命令则应变为禁止。我们可以利用 MFC 的命令更新机制实现此逻辑功能。在菜单下拉
20、之前,或在工具条按钮处在空闲循环期间,MFC 会发一个更新命令,这将导致命令更新处理函数的调用。命令更新处理函数可以根据情况,使用户接口对象(主要指菜单项和工具条按钮)允许或禁止。定义命令更新处理函数的方法如下:按 Ctrl+W 键进入 ClassWizard。 图 4.7 ClassWizard 对话框 如图 4.7 所示,在 Class name 栏中选择CMainFrame,在 Object IDs 栏中选择ID_RECORD_START,在 Messages 栏中双击UPDATE_COMMAND_UI 项,则 ClassWizard 会弹出一个对话框询问命令更新处理函数的名字,使用其提
21、供的函数名即可。按 OK 按钮后,函数OnUpdateRecordStart 就被加入到了 Member functions 栏中。仿照步 2,为 ID_RECORD_STOP 定义一个命令更新处理函数。按 OK 按钮关闭 ClassWizard 对话框。这时读者会发现 CMainFrame 类多了两个成员函数,OnUpdateRecordStart 和 OnUpdateRecordStop。命令更新处理函数有一个参数是 CCmdUI 类的指针,通过调用 CCmdUI 类的成员函数 Enable(TRUE)或Enable(FALSE)可以使用户接口对象允许或禁止。需要给 CMainFrame
22、加一个布尔型成员变量以表明是否正在录音,这样命令更新处理函数可根据这个变量来决定用户接口对象的状态。请读者在 CMainFrame 类内加入下面一行代码:BOOL m_bWorking;接下来请读者按清单 4.3 进行修改。清单 4.3 命令更新处理CMainFrame:CMainFrame()/ TODO: add member initialization code herem_bWorking=FALSE; void CMainFrame:OnRecordStart() / TODO: Add your command handler code hereMessageBeep(UINT)
23、(-1);m_bWorking=TRUE;void CMainFrame:OnRecordStop() / TODO: Add your command handler code hereMessageBeep(UINT)(-1);m_bWorking=FALSE;void CMainFrame:OnUpdateRecordStart(CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-Enable(!m_bWorking); void CMainFrame:OnUpdateRecordStop(
24、CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-Enable(m_bWorking) ;m_bWorking 的初值应是 FALSE,对它的初始化工作在 CMainFrame 的构造函数中完成。m_bWorking 的值在处理 Start 和 Stop 命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了CCmdUI:Enable,该函数根据 m_bWorking 的值来更新命令接口对象。编译并运行 Record,现在 Start 和 Stop 命令的逻辑功能已经实现了。4.2.3 按
25、钮风格在 Record 程序中,用户可以选择两种采样频率来录音。用户接口对象应该能反映出当前的采样频率。普通的工具条按钮在按下后会立刻弹起来,我们希望Record 程序的频率选择按钮具有单选按钮的风格,即当用户选择了一个采样频率时,该采样频率对应的按钮一直处于按下的状态,而另一个频率选择按钮应处于弹起状态。我们可以利用 CCmdUI:SetCheck 函数来实现这一功能,在命令更新函数中调用CCmdUI:SetCheck(TRUE)或CCmdUI:SetCheck(FALSE)可将用户接口对象设定为选中或不选中状态,当一个用户接口对象被选中时,相应的工具按钮会处于按下的状态,并且相应的菜单项的
26、前面会加上一个选中标记。这里需要给 CMainFrame类加一个布尔型成员变量以表明当前的采样频率。请读者在 CMainFrame 类内加入下面一行代码:BOOL m_bHighQuality;接下来请读者按清单 4.4 进行修改。清单 4.4 CMainFrame:CMainFrame()/ TODO: add member initialization code herem_bWorking=FALSE; m_bHighQuality=TRUE;void CMainFrame:OnHighQuality() / TODO: Add your command handler code her
27、em_bHighQuality=TRUE; void CMainFrame:OnLowQuality() / TODO: Add your command handler code herem_bHighQuality=FALSE; void CMainFrame:OnUpdateHighQuality(CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-SetCheck(m_bHighQuality); void CMainFrame:OnUpdateLowQuality(CCmdUI* pCm
28、dUI) / TODO: Add your command update UI handler code herepCmdUI-SetCheck(!m_bHighQuality); m_bHighQuality 的初值是 TRUE,即缺省时是高频采样,对它的初始化工作在 CMainFrame 的构造函数中完成。m_bHighQuality 的值在处理 High quality 和Low quality 命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了 CCmdUI:SetCheck,该函数根据 m_bHighQuality 的值来更新命令接口对象,从而使工具条按钮具有了单选按钮的风
29、格。编译并运行 Record,读者可以看到具有新风格的工具条按钮。当选择采样频率时,相应的菜单项前会出现一个选中标记,相应的工具条按钮会被按下。4.2.4 工具条的隐藏/显示读者可能已经试过了 Record 程序的 View 菜单的功能。通过该菜单用户可以隐藏/显示工具条和状态栏,这个功能是由 AppWizard 自动实现的。由于第二个工具条是手工建立的,因此它不会自动具备隐藏/显示功能。但我们可以通过编程来实现第二个工具条的隐藏/显示:打开 IDR_MAINFRAME 菜单资源在 View 菜单中加入一个名为 Toolbar1 的菜单项,指定其 ID 为 ID_VIEW_TOOLBAR1,并
30、在 Prompt 栏中输入 Show or hide the toolbar1nToggle ToolBar1。按 Ctrl+W 键进入 ClassWizard。在 Class name栏中选择 CMainFrame,在 Object IDs 栏中选择ID_VIEW_TOOLBAR1,并为该命令 ID 定义命令处理函数OnViewToolbar1 和命令更新处理函数OnUpdateViewToolbar1。按清单 4.5 修改程序。清单 4.5 显示/隐藏工具条void CMainFrame:OnViewToolbar1() / TODO: Add your command handler c
31、ode herem_wndToolBar1.ShowWindow(m_wndToolBar1.IsWindowVisible()?SW_HIDE:SW_SHOW); RecalcLayout();void CMainFrame:OnUpdateViewToolbar1(CCmdUI* pCmdUI) / TODO: Add your command update UI handler code herepCmdUI-SetCheck(m_wndToolBar1.IsWindowVisible(); 调用 CWnd:ShowWindow(SW_SHOW)或CWnd:ShowWindow(SW_H
32、IDE)可以显示或隐藏窗口。由于工具条也是窗口,CToolBar 是 CWnd 类的继承类,故该函数也是 CToolBar 的成员。在命令处理函数OnViewToolbar1 中,我们调用 CToolBar:ShowWindow来显示/隐藏工具条,在调用时会利用CWnd:IsWindowVisible 函数作出判断,如果工具条是可见的,就传给 ShowWindow 函数 SW_HIDE 参数以隐藏工具条,否则,就传 SW_SHOW 参数显示工具条。接着要调用 CMainFrame:RecalcLayout 以重新调整主框架窗口的布局。命令更新处理函数 OnUpdateViewToolbar1
33、会根据工具条是否可见使 View-Toolbar1 菜单项选中或不选中。编译并运行 Record,现在 Record 程序已变得很有趣了。至此,读者已经掌握了工具条的一些实用编程技术。 4.3 状态栏的设计与实现 状态栏实际上是个窗口,一般分为几个窗格,每个窗格显示不同的信息。AppWizard 会为应用程序自动创建一个状态栏,该状态栏包括几个窗格,分别用来显示状态栏提示和 CAPS LOCK、NUM LOCK 、SCROLL LOCK 键的状态。在 MFC 中,状态栏的功能由CStatusBar 类实现。创建一个状态栏需要以下几个步骤:构建一个 CStatusBar 对象。调用 CStatu
34、sBar:Create 创建状态栏窗口。调用 CStatusBar:SetIndicators 函数分配窗格,并将状态栏的每一个窗格与一个字符串 ID 相联系。相应的代码读者可以在 Record 工程的CMainFrame:OnCreate 成员函数中找到。如清单 4.6所示。清单 4.6 创建状态栏if (!m_wndStatusBar.Create(this) |!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)TRACE0(“Failed to create status barn“);ret
35、urn -1; / fail to createSetIndicators 函数的第一个参数 indicators 是一个 ID 数组,在 CMainFrame 类所在的 CPP 文件的开头部分可以找到该数组,如清单 4.7 所示。清单 4.7 ID 数组static UINT indicators =ID_SEPARATOR, / status line indicatorID_INDICATOR_CAPS,ID_INDICATOR_NUM,ID_INDICATOR_SCRL,;indicator 数组提供了状态栏窗格的分配信息,它的第一项一般为 ID_SEPARATOR,该 ID 对应的窗
36、格用来显示命令提示信息,后三项都是字符串 ID,读者可以在 String Table 字符串资源中找到这三个字符串分别是 CAP、NUM 和 SCRL。它们对应的三个窗格用来显示键盘的状态。现在让我们来给状态栏再加一个时间窗格,它将用来显示系统时间。显示的格式是 hh:mm:ss,即时:分:秒。首先在 indicators 数组的 ID_SEPARATOR 项之后插入一个名为 ID_INDICATOR_CLOCK 的 ID。然后找到并双击名为 String Table 的字符串资源,打开字符串资源编辑窗口。接着在编辑窗口内按 Insert 键以插入一个新的字符串,请指定字符串的 ID 为ID_
37、INDICATOR_CLOCK,内容为 00:00:00。状态栏将根据字符串的长度来确定相应窗格的缺省宽度,所以指定为 00:00:00 就为时间的显示预留了空间。提示:上述方法不能动态改变窗格宽度,并且有时是不精确的,当系统字体改变时,这种做法可能会导致一些误差。考虑到该方法简单直观,且一般情况下问题不大,故本文用它来举例。如果读者对动态、精确地指定窗格感兴趣,请参看 Visual C+ 5.0 随光盘提供的一个名为 NPP 的 MFC 例子(在samplesmfcgeneralnpp 目录下)。 时间窗格显示的时间必须每隔一秒钟更新一次。更新时间窗格的正文可调用 CStatusBar: S
38、etPaneText 函数,要定时更新,则应利用 WM_TIMER消息。在 Windows 中用户可以安装一个或多个计时器,计时器每隔一定的时间间隔就会自动发出一个WM_TIMER 消息,而这个时间间隔可由用户指定。MFC的 Window 类提供了 WM_TIMER 消息处理函数OnTimer,我们应在该函数内进行更新时间窗格的工作。请读者利用 ClassWizard 给 CMainFrame 类加入WM_TIMER 的消息处理函数 OnTimer 和 WM_CLOSE 消息的处理函数 OnClose,具体方法是在 Class name 栏中选择 CMainFrame,在 Object IDs
39、 栏中选择CMainFrame,在 Messages 栏中找到 WM_TIMER 和WM_CLOSE 项,分别双击之然后按 OK 按钮退出ClassWizard。CMainFrame:OnClose 函数是在关闭主框架窗口是被调用的,程序可以在该函数中做一些清除工作。接下来请按清单 4.8 修改程序。清单 4.8 CMainFrame 类的部分代码int CMainFrame:OnCreate(LPCREATESTRUCT lpCreateStruct)SetTimer(1,1000,NULL);return 0;void CMainFrame:OnTimer(UINT nIDEvent) /
40、 TODO: Add your message handler code here and/or call defaultCTime time;time=CTime:GetCurrentTime();CString s=time.Format(“%H:%M:%S“);m_wndStatusBar.SetPaneText(m_wndStatusBar.CommandToIndex(ID_INDICATOR_CLOCK),s);CFrameWnd:OnTimer(nIDEvent);void CMainFrame:OnClose() / TODO: Add your message handler
41、 code here and/or call defaultKillTimer(1);CFrameWnd:OnClose();在 CMainFrame:OnCreate 函数内调用了CWnd:SetTimer 以安装一个计时器,SetTimer 的第一个参数指定计时器 ID 为 1,第二个参数则规定了计时器的时间间隔为 1000 毫秒即 1 秒。这样,每隔 1 秒OnTimer 函数就会被调用一次。在 OnTimer 函数中,首先构建了一个 CTime 对象,接着调用 CTime 的静态成员函数 GetCurrentTime 以获得当前的系统时间,然后利用 CTime:Format 函数返回一
42、个按时:分:秒的格式表示的字符串,最后调用CStatusBar:SetPaneText 来更新时间窗格显示的正文。SetPaneText 的第一个参数是窗格的索引,对于某一个窗格 ID,可调用 CStatusBar:CommandToIndex来获得索引。在撤销主框架窗口时应关闭计时器,因此在CMainFrame:OnClose 函数内调用了 KillTimer 函数。现在让我们来看一下 CMainFrame 的消息映射,在CMainFrame 类所在 CPP 文件的开始部分可以找到该类的消息映射,如清单 4.9 所示。清单 4.9BEGIN_MESSAGE_MAP(CMainFrame, C
43、FrameWnd)/AFX_MSG_MAP(CMainFrame)ON_WM_CREATE()ON_COMMAND(ID_RECORD_STOP, OnRecordStop)ON_COMMAND(ID_RECORD_START, OnRecordStart)ON_UPDATE_COMMAND_UI(ID_RECORD_START, OnUpdateRecordStart)ON_UPDATE_COMMAND_UI(ID_RECORD_STOP, OnUpdateRecordStop)ON_COMMAND(ID_HIGH_QUALITY, OnHighQuality)ON_COMMAND(ID_L
44、OW_QUALITY, OnLowQuality)ON_UPDATE_COMMAND_UI(ID_HIGH_QUALITY, OnUpdateHighQuality)ON_UPDATE_COMMAND_UI(ID_LOW_QUALITY, OnUpdateLowQuality)ON_COMMAND(ID_VIEW_TOOLBAR1, OnViewToolbar1)ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR1, OnUpdateViewToolbar1)ON_WM_TIMER()ON_WM_CLOSE()/AFX_MSG_MAPEND_MESSAGE_MAP()读者
45、可以看到,在消息映射表中,ClassWizard为消息处理函数和命令处理函数自动加入了消息映射。自动加入的部分呈灰色显示,位于注释行/AFX_MSG_MAP 和/AFX_MSG_MAP 之间。命令处理函数由 ON_COMMAND 宏来映射,命令更新处理函数由ON_UPDATE_COMMAND_UI,而 WM_消息的处理函数由ON_WM_消息宏来映射。提示:今后只要看到/AFX_.的注释对,则说明它们之间的部分是 ClassWizard 自动加入的,这部分呈灰色显示。请不要随便修改它们,更不能把手工加入的部分放在/AFX_.注释对内,否则有可能导致 ClassWizard 出错。 编译并运行 Record ,可以看到状态栏的新变化,最终的界面如图 4.8 所示。 图 4.8 最终的 Record 程序