第四课 工具条和状态栏
在上一课中,同学们已经学到了一些基本的界面设计技术。这一课将指导大家如何设计实现工具条和状态栏,并进一步加深对消息驱动机制的理解。
图4.1 工具条和状态栏
如图4.1所示,工具条和状态栏是一个完善的Windows应用程序的重要组成部分(但不是必需的部分)。工具条一般位于主框架窗口的上部,上面有一些图形按钮。当用户用鼠标在某一按钮上单击时,程序就会执行相应的命令;当鼠标在按钮上停留片刻后,就会弹出一个黄色小窗口并显示工具提示。按钮的图形是它所代表功能的形象表示,人们对于形象图形的辨别速度要快于抽象文字,因此工具条提供了一种比菜单更快捷的用户接口。在一个标准的Windows应用程序中,工具条的大部分按钮执行的命令与菜单命令相同,这样做的目的是能同时提供形象和抽象的用户接口,以方便用户的使用。
状态栏位于主框架窗口的底部,主要用来显示一些提示信息,可细分为几个窗格。状态栏的主要功能是简要解释被选中菜单命令或工具条按钮命令,并显示SCROLL LOCK、NUM LOCK等键的状态。
这一课包括以下几个部分:
工具条的可视化设计
工具条的编程技术
状态栏的设计与实现
本课假定读者要编写一个采样声音的应用程序,并给出一个名为Record的具体例子。当然,这个例子不会真的具有采样声音的功能,它只是用来演示工具条和状态栏的使用。
4.1 工具条的可视化设计
从4.0版开始,Visual C++支持一种新型的工具条资源,这使得工具条的创建比以往更加方便灵活了。在MFC中,工具条的功能由类CToolBar实现。工具条资源和工具条类CToolBar是工具条的两个要素。创建工具条的基本步骤是:1.创建工具条资源。
2.构建一个CToolBar对象。
3.调用CToolBar::Create函数创建工具条窗口。
4.调用CToolBar::LoadToolBar载入工具条资源。
使用缺省配置时,AppWizard会自动创建一个工具条。如图4.1所示,这个工具条包含一些常用按钮,如打开文件、存盘、打印等等。用户可以修改这个工具条,去掉无用的按钮,加入自己需要的按钮。如果用户需要创建两个以上的工具条,则不能完全依赖AppWizard,需要自己手工创建之。本节将分别讨论这两种方法。
4.1.1利用AppWizard自动创建
自动创建工具条很简单,请读者按以下步骤操作:
选择 File->New命令。
在弹出的标签式对话框中选Projects页,然后在该页中选中MFC AppWizard (exe)项,并在Project name一栏中输入Record以创建一个名为Record的工程。按回车或用鼠标点击Create按钮后就进入了MFC AppWizard对话框。
在MFC AppWizard对话框的第一步中选中Single document。这样就会创建一个单文档应用程序,若选择Multiple documents项,则将创建一个多文档应用程序。单文档程序一次只能打开一个窗口,显示一个文档的内容,而多文档程序一次可以打开多个窗口,显示多个文档的内容。
用鼠标点击Finish按钮,并在接着的对话框中按OK按钮。
完成以上操作后,工程Record被创建并被自动载入Developer Studio中。将项目工作区切换到资源视图,并展开资源,就会发现其中有一个名为IDR_MAINFRAME的Toolbar(工具条)资源。用鼠标双击“IDR_MAINFRAME”,Developer Studio会打开一个功能强大的工具条资源编辑窗口,如图4.2所示。该窗口的上部显示出了工具条上的按钮,当用户用鼠标选择某一按钮时,在窗口的下部会显示该按钮的位图。在窗口旁边有一个绘图工具面板和一个颜色面板,供用户编辑按钮位图时使用。
图4.2 工具条资源编辑窗口
提示:如果读者看不到这两个面板,请在Developer Studio的工具条的空白处单击鼠标右键,并在随之弹出的菜单中选中Graphics和Colors两项。 |
在修改工具条以前,首先要修改菜单资源。请按以下几步修改菜单资源:
将项目工作区切换至资源视图,选择并打开menu(菜单)资源类型,双击名为IDR_MAINFRAME的菜单资源。
删除Edit菜单。
删除File菜单中除Exit以外的所有菜单项。
在File菜单后插入一个名为&Record的新菜单,并在该菜单中插入&Start和St&op两个菜单项,它们的命令ID(标识符)分别为ID_RECORD_START和ID_RECORD_STOP。Start表示开始录音,而Stop表示停止录音。
修改后的菜单如图4.3所示。
图4.3 修改后的菜单
接下来的任务是修改工具条资源,具体步骤是:
选择并打开Toolbar(工具条)资源类型,双击名为IDR_MAINFRAME的工具条资源以打开相应的资源编辑窗口。
删除“?”按钮前面的所有按钮,删除的方法是用鼠标将要删除的按钮拖出工具条即可。
先选中“?”按钮后面的空白按钮,然后在该按钮的放大位图上用红色画一个实心圆圈,以表示开始录音功能。再选中空白按钮,并用黑色在放大位图上画一个实心矩形,以表示停止功能。
通过用鼠标拖动按钮调整按钮的位置,调整后的位置如图4.4所示。
图4.4 修改后的工具条资源
分别为两个新加的按钮指定命令ID为ID_RECORD_START和ID_RECORD_STOP。指定ID的方法是先选中一个按钮,接着按回车键,在弹出的属性对话框中输入ID(或从ID下拉列表中分别选择ID_RECORD_START和ID_RECORD_STOP)。注意到这两个按钮的ID与Record菜单中的两个菜单项Start和Stop的ID相同,这样同样的命令既可以通过菜单执行,也可以通过工具条执行。
为两个新加的按钮指定命令提示。请分别在两个按钮的属性对话框中的Prompt栏内输入Start record/nStart和Stop record/nStop。命令提示实际上是作为字符串保存在String Table字符串资源中的。命令提示用来解释命令的意义,分状态栏提示和工具提示两种,在Prompt栏中,二者由/n分隔开。当鼠标移动到某个菜单项或工具条上的按钮时,在状态栏中就会显示状态栏提示,当鼠标在某个按钮上停留片刻后,工具提示就会在一个黄色的弹出式窗口中显示出来。输入完成后,读者会发现Record菜单中的两个菜单项被自动加入了相同的提示信息,这说明两个按钮与两个菜单项确实是相对应的。
提示:如果觉得按钮太小,读者可以用鼠标拖动围绕按钮放大位图的虚框的右下角,把按钮放大些。注意工具条内的所有按钮都将被放大 |
修改完后,读者可以编译并运行Record,来看看修改的结果。读者很快会注意到Start和Stop菜单项及按钮都是灰色的。这个现象是正常的,其原因将在4.2节解释。有趣的是工具条可以被拖动(请在工具条的空白地方拖动)并停泊在主框架窗口的任何其它边上,并且工具条是可以浮动的,即当用鼠标双击工具条的空白处时,工具条变成了一个浮动窗口,可被拖动到屏幕上的任意地方。这些有趣的现象将在4.2节解释。不管怎么说,创建和修改工具条的任务已经完成了。4.1.2手工创建
如果想要再加一个工具条,那么AppWizard就无能为力了,必须手工创建。假设Record程序的声音采样频率有11KHZ和44KHZ两档选择,现在我们的任务是再创建一个工具条,可让用户对这两种档次进行选择。本来这样的功能应该位于第一个工具条内,但为了演示工具条的手工创建,这里不妨来个多此一举。
如果Record工程不在Developer Studio中,请选择命令File->Open Workspace打开Record工程。首先要对原来的菜单进行修改,步骤如下:
打开IDR_MAINFRAME菜单资源
双击Record菜单底端的空白项,在其属性窗口中选中Separator,这样就加入了一条分隔线。
在分隔线下面加入两个菜单项,其属性如表4.1所示。
表4.1 菜单项的属性
Caption |
ID |
Prompt |
&Low quality |
ID_LOW_QUALITY |
Low quality(11k)/n11k |
&High quality |
ID_HIGH_QUALITY |
High quality(44k)/n44k |
接着要创建一个新的工具条资源,请按以下步骤进行:
选择Insert->Resource命令,然后在Insert Resource对话框中选中Toolbar。按了OK按钮后,在Toolbar资源类下就会出现一个ID为IDR_TOOLBAR1的新资源。
在新工具条中加入两个按钮,如图4.5所示。每个按钮上都画了一些竖线,线稀的按钮代表低频率采样,线密的按钮代表高频率采样。
图4.5 新创建的工具条资源
分别为两个新加的按钮指定命令ID为ID_LOW_QUALITY和ID_HIGH_QUALITY。
要把这个新设计的工具条加入到程序中,需要在程序中加入一些与创建有关的源代码。在创建第一个工具条时,AppWizard在程序中自动加入了创建源代码,通过仿制这些代码,我们很容易创建出第二个工具条。
在MFC中,工具条的功能由类CToolBar实现。工具条实际上是主框架窗口的子窗口,因此工具条对象应该依附于主框架窗口对象。在AppWizard创建的MFC程序中,主框架窗口的类名是CMainFrame,该类是MFC标准类CFrameWnd类的派生类。将项目工作区切换至类视图并展开CMainFrame类,读者会发现该类有一个名为m_wndToolbar的成员。双击该成员,则Developer Studio会自动打开类CMainFrame所在的头文件,并将光标停在对m_wndToolbar成员的定义处。
提示:在类视图中双击某一个类名,则该类所在的头文件会自动打开。若双击某一个类的成员,则会自动切换到对该成员的定义处。 |
对m_wndToolBar的定义如下:
CToolBar m_wndToolBar;
由此可见m_wndToolBar是一个CToolBar对象,它是CMainFrame的成员。现在请紧接着该成员加入一个新的成员:
CToolBar m_wndToolBar1;
m_wndToolBar1代表第二个工具条。读者不要以为给CMainFrame加入一个CToolBar对象就完事了。实际的创建工具条的工作不会在构造CToolBar对象时完成,只有调用了类CToolBar的一些成员函数后,创建工作才能结束。
对工具条的实际创建工作在CMainFrame::OnCreate函数中完成。OnCreate函数是在创建窗口时被调用的,这时窗口的创建已部分完成,窗口对象的m_hWnd成员中存放的HWND句柄也已有效,但窗口还是不可见的。因此一般在OnCreate函数中作一些诸如创建子窗口的初始化工作。
提示:初学者一个易犯的错误是在构造函数而不是在OnCreate中创建子窗口。在构造函数中,父窗口并没有创建,如果在这时创建子窗口,则将会因为得不到父窗口的有效HWND句柄而导致创建失败。 |
找到CMainFrame::OnCreate函数,对该函数进行一些修改,修改的部分如清单4.1的黑体字所示。在以后,凡是程序中手工修改的部分,一般都会用黑体显示。
清单4.1 修改后的CMainFrame::OnCreate函数
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if (!m_wndToolBar1.Create(this) ||
!m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_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 don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar1.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
DockControlBar(&m_wndToolBar1);
return 0;
} 注意在CMainFrame::OnCreate函数首先调用了CFrameWnd::OnCreate。这行代码是AppWizard自动加入的,但有必要解释一下。CMainFrame是CFrameWnd类的继承类,在CMainFrame::OnCreate中首先要调用基类CFrameWnd的OnCreate函数,因为基类也要进行一些初始化工作,而基类的OnCreate函数不会自动调用,因此需要在继承类的OnCreate函数中显式调用。OnCreate实际上是WM_CREATE消息的消息处理函数,读者可能要问,为什么是派生类的OnCreate处理WM_CREATE消息,而不是基类的OnCreate呢。如果读者仔细观察OnCreate函数在CMainFrame类头文件中的说明,就会发现在该函数前有一个afx_msg前缀。afx_msg的作用与virtual关键字类似,它使得被说明的函数有虚拟函数的特性,即由继承类而不是基类的处理函数来处理消息。
读者可以看出黑体的代码是仿照第一个工具条的创建代码进行编写的。事实上,笔者不过是将原来的代码复制一份,然后在略作修改而已。
提示: 读者不必为这种“抄袭”行为感到羞愧。由于Visual C++博大精深,各种类和函数成百上千,除非你有外星人一般的记忆力,否则是不可能记住所有东西的。用Visual C++编程,重要的是理解而不是记忆。只要你理解了程序的来龙去脉,就可以最大限度的利用现有的成熟代码,提高程序的开发效率和可靠性。 |
对第二个工具条的创建代码的解释是:首先,调用CToolBar::Create以创建工具条窗口,注意Create函数的参数是this指针,这是因为主框架窗口是工具条的父窗口。接着调用CToolbar::LoadToolBar(IDR_TOOLBAR1)以载入工具条资源。然后调用CToolBar::SetBarStyle指定工具条的风格,在调用该函数时先调用CToolBar::GetBarStyle取得工具条的风格,然后在原有风格的基础上又指定了CBRS_TOOLTIPS、 CBRS_FLYBY和CBRS_SIZE_DYNAMIC风格,这使得工具条可显示工具提示,并可以动态改变尺寸。接着调用CToolBar::EnableDocking(CBRS_ALIGN_ANY)使工具条是可以停泊的,但还需调用CFrameWnd::EnableDocking(CBRS_ALIGN_ANY),只有这样才能实现可停泊的工具条。最后调用CFrameWnd::DockControlBar以停泊工具条。
编译并运行Record看看,现在Record程序已经拥有两个工具条了。至此创建工具条的任务已经完成,下面需要对工具条编程,以使其能够发挥执行命令的功能。
4.2工具条的编程技术
本节将讨论一些与工具条有关的编程技术,主要包括命令处理、命令更新、按钮风格和工具条的隐藏/显示等技术。4.2.1命令处理
要使菜单和工具条执行命令,光为它们指定命令ID是不行的,必须为每个命令ID定义命令处理函数。如果不为命令定义命令处理函数或下面将要提到的命令更新处理函数,则框架将自动使该命令对应的菜单项和按钮禁止(灰化),这就是4.1节中的工具条按钮和菜单项灰化的原因。
利用ClassWizard可以很方便地加入命令处理函数,请读者按以下步骤操作:
按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按钮关闭ClassWizard对话框。这时读者会发现CMainFrame类多了两个成员函数,OnRecordStart和OnRecordStop。
现在要在这两个命令处理函数中插入相应的源代码以实现其功能。当然,这里不会真的实现开始录音和停止录音的功能。我们只是让这两个函数发出一个声音,象征性地表示功能的执行,具体代码如清单4.2所示。
清单4.2 OnRecordStart和OnRecordStop函数
void CMainFrame::OnRecordStart()
{
// TODO: Add your command handler code here
MessageBeep((UINT)(-1));
}
void CMainFrame::OnRecordStop()
{
// TODO: Add your command handler code here
MessageBeep((UINT)(-1));
}
编译并运行Record,可以看到Start和Stop命令已经可以执行了。
4.2.2命令更新
虽然Start和Stop命令可以执行了,但是还有一个不足之处。在没有开始录音之前,Stop命令应该是禁止的,也即对应的菜单项和按钮应是禁止的,这是因为此时没有必要执行该命令。录音开始后,Stop命令应该允许,而Start命令则应变为禁止。我们可以利用MFC的命令更新机制实现此逻辑功能。
在菜单下拉之前,或在工具条按钮处在空闲循环期间,MFC会发一个更新命令,这将导致命令更新处理函数的调用。命令更新处理函数可以根据情况,使用户接口对象(主要指菜单项和工具条按钮)允许或禁止。定义命令更新处理函数的方法如下:
按Ctrl+W键进入ClassWizard。
图4.7 ClassWizard对话框
如图4.7所示,在Class name栏中选择CMainFrame,在Object IDs栏中选择ID_RECORD_START,在Messages栏中双击UPDATE_COMMAND_UI项,则ClassWizard会弹出一个对话框询问命令更新处理函数的名字,使用其提供的函数名即可。按OK按钮后,函数OnUpdateRecordStart就被加入到了Member functions栏中。
仿照步2,为ID_RECORD_STOP定义一个命令更新处理函数。
按OK按钮关闭ClassWizard对话框。这时读者会发现CMainFrame类多了两个成员函数,OnUpdateRecordStart和OnUpdateRecordStop。
命令更新处理函数有一个参数是CCmdUI类的指针,通过调用CCmdUI类的成员函数Enable(TRUE)或Enable(FALSE)可以使用户接口对象允许或禁止。需要给CMainFrame加一个布尔型成员变量以表明是否正在录音,这样命令更新处理函数可根据这个变量来决定用户接口对象的状态。请读者在CMainFrame类内加入下面一行代码:
BOOL m_bWorking;
接下来请读者按清单4.3进行修改。
清单4.3 命令更新处理
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_bWorking=FALSE;
}
void CMainFrame::OnRecordStart()
{
// TODO: Add your command handler code here
MessageBeep((UINT)(-1));
m_bWorking=TRUE;
}
void CMainFrame::OnRecordStop()
{
// TODO: Add your command handler code here
MessageBeep((UINT)(-1));
m_bWorking=FALSE;
}
void CMainFrame::OnUpdateRecordStart(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(!m_bWorking);
}
void CMainFrame::OnUpdateRecordStop(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->Enable(m_bWorking) ;
}
m_bWorking的初值应是FALSE,对它的初始化工作在CMainFrame的构造函数中完成。m_bWorking的值在处理Start和Stop命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了CCmdUI::Enable,该函数根据m_bWorking的值来更新命令接口对象。
编译并运行Record,现在Start和Stop命令的逻辑功能已经实现了。
4.2.3按钮风格
在Record程序中,用户可以选择两种采样频率来录音。用户接口对象应该能反映出当前的采样频率。普通的工具条按钮在按下后会立刻弹起来,我们希望Record程序的频率选择按钮具有单选按钮的风格,即当用户选择了一个采样频率时,该采样频率对应的按钮一直处于按下的状态,而另一个频率选择按钮应处于弹起状态。
我们可以利用CCmdUI::SetCheck函数来实现这一功能,在命令更新函数中调用CCmdUI::SetCheck(TRUE)或CCmdUI::SetCheck(FALSE)可将用户接口对象设定为选中或不选中状态,当一个用户接口对象被选中时,相应的工具按钮会处于按下的状态,并且相应的菜单项的前面会加上一个选中标记。这里需要给CMainFrame类加一个布尔型成员变量以表明当前的采样频率。请读者在CMainFrame类内加入下面一行代码:
BOOL m_bHighQuality;
接下来请读者按清单4.4进行修改。
清单4.4
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_bWorking=FALSE;
m_bHighQuality=TRUE;
}
void CMainFrame::OnHighQuality()
{
// TODO: Add your command handler code here
m_bHighQuality=TRUE;
}
void CMainFrame::OnLowQuality()
{
// TODO: Add your command handler code here
m_bHighQuality=FALSE;
}
void CMainFrame::OnUpdateHighQuality(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_bHighQuality);
}
void CMainFrame::OnUpdateLowQuality(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(!m_bHighQuality);
}
m_bHighQuality的初值是TRUE,即缺省时是高频采样,对它的初始化工作在CMainFrame的构造函数中完成。m_bHighQuality的值在处理High quality和Low quality命令时会被更新以反映当前的状态。两个命令更新处理函数都调用了CCmdUI::SetCheck,该函数根据m_bHighQuality的值来更新命令接口对象,从而使工具条按钮具有了单选按钮的风格。
编译并运行Record,读者可以看到具有新风格的工具条按钮。当选择采样频率时,相应的菜单项前会出现一个选中标记,相应的工具条按钮会被按下。
4.2.4工具条的隐藏/显示
读者可能已经试过了Record程序的View菜单的功能。通过该菜单用户可以隐藏/显示工具条和状态栏,这个功能是由AppWizard自动实现的。由于第二个工具条是手工建立的,因此它不会自动具备隐藏/显示功能。但我们可以通过编程来实现第二个工具条的隐藏/显示:
打开IDR_MAINFRAME菜单资源
在View菜单中加入一个名为Toolbar1的菜单项,指定其ID为ID_VIEW_TOOLBAR1,并在Prompt栏中输入Show or hide the toolbar1/nToggle 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 code here
m_wndToolBar1.ShowWindow(m_wndToolBar1.IsWindowVisible()?
SW_HIDE:SW_SHOW);
RecalcLayout();
}
void CMainFrame::OnUpdateViewToolbar1(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_wndToolBar1.IsWindowVisible());
}
调用CWnd::ShowWindow(SW_SHOW)或CWnd::ShowWindow(SW_HIDE)可以显示或隐藏窗口。由于工具条也是窗口,CToolBar是CWnd类的继承类,故该函数也是CToolBar的成员。在命令处理函数OnViewToolbar1中,我们调用CToolBar::ShowWindow来显示/隐藏工具条,在调用时会利用CWnd::IsWindowVisible函数作出判断,如果工具条是可见的,就传给ShowWindow函数SW_HIDE参数以隐藏工具条,否则,就传SW_SHOW参数显示工具条。接着要调用CMainFrame::RecalcLayout以重新调整主框架窗口的布局。
命令更新处理函数OnUpdateViewToolbar1会根据工具条是否可见使View->Toolbar1菜单项选中或不选中。
编译并运行Record,现在Record程序已变得很有趣了。至此,读者已经掌握了工具条的一些实用编程技术。
4.3 状态栏的设计与实现
状态栏实际上是个窗口,一般分为几个窗格,每个窗格显示不同的信息。AppWizard会为应用程序自动创建一个状态栏,该状态栏包括几个窗格,分别用来显示状态栏提示和CAPS LOCK、NUM LOCK 、SCROLL LOCK键的状态。在MFC中,状态栏的功能由CStatusBar类实现。
创建一个状态栏需要以下几个步骤:
构建一个CStatusBar对象。
调用CStatusBar::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 bar/n");
return -1; // fail to create
}
…
SetIndicators函数的第一个参数indicators是一个ID数组,在CMainFrame类所在的CPP文件的开头部分可以找到该数组,如清单4.7所示。
清单4.7 ID数组
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
indicator数组提供了状态栏窗格的分配信息,它的第一项一般为ID_SEPARATOR,该ID对应的窗格用来显示命令提示信息,后三项都是字符串ID,读者可以在String Table字符串资源中找到这三个字符串分别是CAP、NUM和SCRL。它们对应的三个窗格用来显示键盘的状态。
现在让我们来给状态栏再加一个时间窗格,它将用来显示系统时间。显示的格式是hh:mm:ss,即时:分:秒。
首先在indicators数组的ID_SEPARATOR项之后插入一个名为ID_INDICATOR_CLOCK的ID。然后找到并双击名为String Table的字符串资源,打开字符串资源编辑窗口。接着在编辑窗口内按Insert键以插入一个新的字符串,请指定字符串的ID为ID_INDICATOR_CLOCK,内容为00:00:00。状态栏将根据字符串的长度来确定相应窗格的缺省宽度,所以指定为00:00:00就为时间的显示预留了空间。
提示:上述方法不能动态改变窗格宽度,并且有时是不精确的,当系统字体改变时,这种做法可能会导致一些误差。考虑到该方法简单直观,且一般情况下问题不大,故本文用它来举例。如果读者对动态、精确地指定窗格感兴趣,请参看Visual C++ 5.0随光盘提供的一个名为NPP的MFC例子(在samples/mfc/general/npp目录下)。 |
时间窗格显示的时间必须每隔一秒钟更新一次。更新时间窗格的正文可调用CStatusBar:: SetPaneText函数,要定时更新,则应利用WM_TIMER消息。在Windows中用户可以安装一个或多个计时器,计时器每隔一定的时间间隔就会自动发出一个WM_TIMER消息,而这个时间间隔可由用户指定。MFC的Window类提供了WM_TIMER消息处理函数OnTimer,我们应在该函数内进行更新时间窗格的工作。
请读者利用ClassWizard给CMainFrame类加入WM_TIMER的消息处理函数OnTimer和WM_CLOSE消息的处理函数OnClose,具体方法是在Class name栏中选择CMainFrame,在Object IDs栏中选择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)
{
// TODO: Add your message handler code here and/or call default
CTime 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 code here and/or call default
KillTimer(1);
CFrameWnd::OnClose();
}
在CMainFrame::OnCreate函数内调用了CWnd::SetTimer以安装一个计时器,SetTimer的第一个参数指定计时器ID为1,第二个参数则规定了计时器的时间间隔为1000毫秒即1秒。这样,每隔1秒OnTimer函数就会被调用一次。
在OnTimer函数中,首先构建了一个CTime对象,接着调用CTime的静态成员函数GetCurrentTime以获得当前的系统时间,然后利用CTime::Format函数返回一个按时:分:秒的格式表示的字符串,最后调用CStatusBar::SetPaneText来更新时间窗格显示的正文。SetPaneText的第一个参数是窗格的索引,对于某一个窗格ID,可调用CStatusBar::CommandToIndex来获得索引。
在撤销主框架窗口时应关闭计时器,因此在CMainFrame::OnClose函数内调用了KillTimer函数。
现在让我们来看一下CMainFrame的消息映射,在CMainFrame类所在CPP文件的开始部分可以找到该类的消息映射,如清单4.9所示。
清单4.9
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{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_LOW_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_MAP
END_MESSAGE_MAP()
读者可以看到,在消息映射表中,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程序
小 结
本章主要向读者介绍了工具条和状态栏的一些实用技术。要点如下:
在MFC中,创建一个窗口一般分两步:1.构建一个窗口对象。构建的方法是定义一个对象或用new操作符动态创建之。2.调用窗口类的Create成员函数。该函数把实际的窗口作出来,并将其HWND保存在窗口的公共数据成员m_hWnd中。
创建工具条和状态栏的工作是在CMainFrame::OnCreate函数中完成的,OnCreate函数是在创建窗口时被调用的,这时窗口的创建已部分完成,窗口对象的HWND句柄也已有效,但窗口还是不可见的。因此一般在OnCreate函数中作一些诸如创建子窗口的初始化工作。
afx_msg前缀保证了正确版本的消息处理函数被调用。
工具条有两个要素:工具条资源和工具条类CToolBar。若用户只需要一个工具条,可利用AppWizard自动生成,然后再修改之。若需要多个工具条,则必须手工创建。
如果不为命令定义命令处理函数或命令更新处理函数,则框架将自动使该命令对应的用户接口对象(主要指菜单项和按钮)禁止。利用ClassWizard可以十分方便的加入命令处理函数和命令更新处理函数。
在菜单下拉之前,或在工具条按钮处在空闲循环期间,MFC会发一个更新命令,这将导致命令更新处理函数的调用。命令更新处理函数利用CCmdUI类来更新用户接口对象。调用CCmdUI::Enable可使用户接口对象允许或禁止,调用CCmdUI::SetCheck可使用户接口对象选中或不选中。
调用CWnd::ShowWindow可以隐藏/显示一个窗口。
要在状态栏中插入新的窗格,需要在indicator数组中插入新的字符串ID。而状态栏将根据这个字符串的长度来确定新窗格的缺省宽度。
调用CStatusBar:: SetPaneText可更新状态栏窗格显示的正文。