现在我们开始编写全书的第一个程序。跟我们以前学习程序设计的方法不同(以前我们是输入完整程序,然后运行),我们首先利用Visual Studio的可视化编程工具AppWizard生成框架程序,再往里边填写代码。这是一种“填空式”的编程方法:首先生成框架,然后根据目标程序的要求,看哪些地方需要修改,再往里填写代码。类似其他语言,我们把第一个程序命名为Hello。
首先启动AppWizard:在File菜单下选择New,弹出New对话框,如图3.1所示。
图3.1 New对话框
在对话框顶部有一排标签,用于选择要创建的文档的类型。选择Projects标签,然后在列表中选择MFC AppWizard(exe),告诉Visual C++要使用AppWizard创建一个EXE程序;在Project Name编辑框中输入工程文件名Hello,在Location中指定应用程序创建的位置,缺省情况下AppWizard会自动在当前目录下以工程文件名为名字创建一个新目录,在该目录下存放所有该工程的文件。这里将目录设置为“C:/Hello”,然后选择OK按钮,此时弹出MFC-AppWizard-Step1对话框,如图3.2所示。
图3.2 MFC AppWizard-Step 1对话框
AppWizard是一个自动化程序生成工具,它通过提示用户一系列对话框,来指定将要生成的应用程序的特性,然后自动生成相应的代码。下面,我们一步一步演示如何用AppWizard生成Hello程序。
1.MFC AppWizard当前显示MFC AppWizard-Step1对话框。在这个对话框中,可以指定生成框架的类型,包括Single Document(单文档),Multiple Document(多文档),Dialog Based(基于对话框)三种。还可以从下拉列表框中选择语言,指定程序资源文件使用的语言类型。选择Single Document,此时AppWizard将生成一个单文档的应用程序框架,也就是说,应用程序运行时是一个单窗口的界面。点击Next按钮。
图3.3 MFC AppWizard-Step 2 of 6对话框
2.此时MFC AppWizard显示图3.3所示MFC AppWizard-Step 2 of 6对话框。该对话框用于指定数据库选项。MFC AppWizard支持数据库并可以生成数据库应用程序所必需的代码。选择缺省值None,不使用数据库特性。点击Next按钮,弹出MFC AppWizard-Step3对话框,如图3.4所示。
图3.4 MFC AppWizard-Step 3 of 6对话框
3.在MFC AppWizard-Step 3 of 6对话框中,可以指定OLE选项的复合文档类型。因为我们不用OLE特性,所以使用缺省值None。点击Next按钮,进入下一个对话框。此时,屏幕显示MFC-AppWizard-Step 4 of 6对话框,如图3.5。
图3.5 MFC AppWizard-Step 4 of 6对话框
4.第四个对话框用于指定应用程序的外观,包括是否使用工具条、状态栏,是否让文档支持打印和打印预览功能,是否使用3D控制外观,以及是否支持在线帮助等。MFC AppWizard还支持WOSA(Windows开放系统体系结构),可以直接在基于文档的程序中加入MAPI电子邮件发送功能和WinSocket网络编程接口支持。另外,还可以指定文档的一些特性,包括后缀名等。有关文档/视结构的内容在后面章节中再作详细介绍。按照图3.5所示,设置各个选项,它支持工具条、状态栏,使用3D外观的控制。点击Next按钮,弹出MFC AppWizard 5 of 6对话框,如图3.6所示。
图3.6 MFC AppWizard-Step 5 of 6对话框
5.MFC AppWizard 5of 6有两个选项,让用户设置生成源代码的选项及编译选项。
第一个选项是:Would you like to generate source file comments?询问MFC AppWizard是否为生成的源代码添加必要的注释说明。注释说明有助于对应用程序源代码的学习和理解,因此一般选择“Yes,Please”。
第二个选项是:How would you like to use the MFC library?
用户可以选择As a shared DLL(使用共享动态连接库)或As a static linked library(静态连接库)。使用DLL时,所有MFC的类存放在动态连接库中,因此可以使应用程序小一些,但是发布该应用程序可执行文件时必需随同提供必要的动态连接库。使用静态库时,应用程序所用到的MFC类都编译进了可执行文件之中,因此可执行文件比使用DLL方式的要大,但可以单独发行。一般的,对于小的应用程序可以采用静态库方式,对于大的应用程序一般采用动态连接库方式。本书中的例子全部采用动态连接库选项(As a shared DLL)。
6.点击Next按钮以进入MFC AppWizard的最后一个对话框。此时,将出现如图3.7所示的对话框。
图3.7 MFC AppWizard-Step 6 of 6对话框
该对话框让用户选择MFC AppWizard将要创建的类的属性,包括指定存放类的文件名以及这些类的基类。在这一步,用户可以修改所创建的类的名字、对应的文件以及赖以派生的父类。对于Hello程序,直接对话框中的使用缺省设置。
7.点Finish按钮,弹出New Project Information对话框。它给出一个关于即将生成的应用程序的总体描述,包括应用程序类型(单文档)、要创建的类及所在文件、应用程序的一些特性(是否支持工具条、状态栏等)。按Enter键或点OK按钮,此时AppWizard将在c:/hello目录下生成Hello程序所有的框架文件。生成程序后,在项目工作区中自动打开Hello.dsw项目工作区文件。窗口标题将显示打开的项目名Hello。
3.2 AppWizard所创建的文件
AppWizard在读者指定的C:/Hello目录下创建了许多文件,这些文件包含了框架程序的所有的类、全局变量的声明和定义。初学者面对这一大堆文件可能会不知所措。现在我们把各个文件的作用及对应的类介绍一下。
根据可选项,AppWizard所创建的文件会略有不同。标准的AppWizard文件包括:
-
工作区文件、项目文件和make文件
-
应用程序源文件和头文件
-
资源文件
-
预编译头文件
-
按可选项增加的AppWizard文件
3.2.1 工作区、项目文件和make文件
Hello.dsw
这是MFC自动生成的工作区文件,它包含当前工作区所包含的项目的信息。
Hello.dsp
这是MFC生成的项目文件,它包含当前项目的设置、所包含的文件等信息。
Hello.MAK
这是MFC项目的项目文件,这也是与NMAKE兼容的文件。如果选择了External make文件可选项,则可人工对它编辑,但不能利用Visual C++许多项目编辑特性。
Hello.CLW
这个文件含有被ClassWizard用来编辑现有类或增加新类的信息。ClassWizard还用这个文件来保存创建和编辑消息映射和对话框数据所需的信息,或是创建虚拟成员函数所需的信息。3.2.2 应用程序源文件和头文件
根据应用程序的类型—单文档、多文档或基于对话框的,AppWizard将创建下述应用程序源文件和头文件中的某些文件。在本例中,AppWizard生成了如下文件:
Hello.h
这是应用程序的主头文件,它含有所有全局符号和用于包含其它头文件的#include伪指令。
Hello.CPP
这个文件是应用程序的主源文件。它将创建CHelloApp类的一个对象(从CWinApp派生),并覆盖InitInstance成员函数。
MainFrm.cpp,MainFrm.h
这两个文件将从CFrameWnd(SDI应用程序)或CMDIFrameWnd(MDI应用程序)派生CMainFrame类。如果在AppWizard的Application Options页(6步中的第4步)中选择了对应的可选项的话,CMainFrame类将处理工具条按钮和状态条的创建。MAINFRM.CPP文件还含有MFC应用程序提供的默认工具条按钮的对象ID——叫做buttons数组。
HelloDoc.cpp,HelloDoc.h
这些文件从CDocument类派生并实现名为CHelloDoc的文档类,并含有用于初始化文档、串行化(保存和装入)文档和用于调试诊断的一些成员函数的框架。
HelloView.cpp,HelloView.h
这些文件派生并实现名为CHelloView的视类,用于显示和打印文档数据。CHelloView类是从CView或它的派生类派生出来的,含有绘制视和用于调试诊断的一些成员函数框架。3.2.3 资源文件
AppWizard会创建一些与资源相关的文件。
Hello.RC,RESOURCE.H,Hello.rc2
这是项目的头文件及其资源文件。资源文件含有一般MFC应用程序的默认菜单定义和加速键表、字符串表。它还指定了缺省的About对话框和一个图标文件(RES/Hello. ICO)。资源文件了标准的MFC类的资源。如果指定了支持工具条,它还将指定工具条位图文件(RES/TOOLBAR.BMP)。Hello.rc2用于存放Visual Studio不可直接编辑的资源。3.2.4 预编译头文件:STDAFX.CPP,STDAFX.H
这两个文件用于建立一个预编译的头文件Hello.PCH和一个预定义的类型文件STDAFX.OBJ。由于MFC体系结构非常大,包含许多头文件,如果每次都编译的话比较费时。因此,我们把常用的MFC头文件都放在stdafx.h中,如afxwin.h、afxext.h、afxdisp.h、afxcmn.h等,然后让stdafx.cpp包含这个stdafx.h文件。这样,由于编译器可以识别哪些文件已经编译过,所以stdafx.cpp就只编译一次,并生成所谓的预编译头文件(因为它存放的是头文件编译后的信息,故名)。如果读者以后在编程时不想让有些MFC头文件每次都被编译,也可以将它加入到stdafx.h中。采用预编译头文件可以加速编译过程。
3.3 编译和链接Hello程序
虽然我们到现在为止还没有写任何一行代码,但我们确实得到了一个完整的可运行的程序。要编译运行程序,可以选择Build-(或按快捷键F7),编译该程序。编译完后再选择Build-Execute Hello.exe(或按快捷键CTRL+F5),运行该程序。也可以直接按CTRL+F5,系统提示是否编译,回答“Yes”,Visual Studio将自动编译链接并运行Hello.exe程序。
提示:在Build菜单下有Compile,Build,Rebuild All三个菜单项用于编译程序。其中Compile用于编译当前打开的活动文档;Build只编译工程中上次修改过的文件,并链接程序生成可执行文件。如果以前没有作过编译,它会自动调用Rebuild All操作,依次编译资源文件、源程序文件等;Rebuild All不管文件是否作过修改,都会编译工程中的所有源文件。由于编译链接过程中会产生大量的中间文件和目标文件,它们占用许多硬盘空间,因此Visual Studio在Build下提供了Clean菜单项用于清除这些中间文件。用户在完成一个工程后,应及时清理这些中间文件,否则硬盘很快会被耗尽。 |
Hello.exe程序执行后,显示如图3.8所示的窗口。窗口标题为Untitled-Hello。现在我们要在窗口内显示“Hello,world”字样,为此需要手工编辑代码。在类视图(ClassView)中点CHelloView前面的加号,展开CHelloView树,显示它的类成员函数和数据成员。双击OnDraw函数,Visual Studio将打开HelloView.cpp文件并将光标定位在OnDraw函数定义开始处。在OnDraw函数中手工加入代码,如下所示(黑体字为自己加入的代码)。
void CHelloView::OnDraw(CDC* pDC)
{
CHelloDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CString str("Hello,World!");
pDC->TextOut(10,10,str);
}
编译并运行该程序,弹出如图3.9所示的窗口。在窗口左上角显示“Hello,World!”。
图3.9 显示“Hello,World!”的窗口
到现在为止,我们只写了两行代码,就完成了SDK下需要几百行程序才能完成的工作。这应当归功于Visual Studio提供的AppWizard以及MFC框架在幕后所作的大量工作。为了更好的理解和设计基于MFC框架的程序,我们分析一下MFC框架所做的工作。
3.4 应用程序执行机制3.4.1 WinMain函数
在DOS下,程序的执行是从main函数开始的。在Windows下,对应的函数是WinMain。但是,如果浏览Hello程序的所有的方法和全局函数,是找不到WinMain函数的。MFC考虑到典型的Windows程序需要的大部分初始化工作都是标准化的,因此把WinMain函数隐藏在应用程序的框架中,编译时会自动将该函数链接到可执行文件中。程序员可以重写WinMain函数,但一般不需要这么做。
下面的程序清单3-1给出了WinMain函数的代码。其中,_tWinMain函数在/DevStudio/Vc/Mfc/src/AppModul.cpp中定义,它所调用的AfxWinMain函数在同一目录下的WinMain.cpp中定义。名字是_tWinMain函数而不是WinMain,是考虑到对不同字符集的支持,在tchar.h中有_tWinMain的宏定义。在ANSI字符集下编译时,_tWinMain就变成WinMain,在Unicode下编译时,_tWinMain就变成wWinMain。提示:Unicode是具有固定宽度、统一的文本和字符的编码标准。由于Unicode采用的是16位编码,因此可以包含世界各地的书写系统的字符和技术符号(如中文也在Unicode之中),从而克服了ASCII码在表示多语言文本上的不足之处,扩大了ASCII码7位编码方案的好处。Unicode同等地对待所有的字符,并且在表示各种语言的任何字符时既不需要换码序列(escape)也不需要控制代码。Win32和Visual C++很好的支持Unicode字符集。清单3-1 _tWinMain函数定义
// export WinMain to force linkage to this module
extern int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow);
#ifdef _MAC
extern "C" int PASCAL
#else
extern "C" int WINAPI
#endif
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
AfxWinMain函数定义:
/
// Standard WinMain implementation
// Can be replaced as long as 'AfxWinInit' is called first
int AFXAPI AfxWinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
goto InitFailure;
// App global initializations (rare)
ASSERT_VALID(pApp);
if (!pApp->InitApplication())
goto InitFailure;
ASSERT_VALID(pApp);
// Perform specific initializations
if (!pApp->InitInstance())
{
if (pApp->m_pMainWnd != NULL)
{
TRACE0("Warning: Destroying non-NULL m_pMainWnd/n");
pApp->m_pMainWnd->DestroyWindow();
}
nReturnCode = pApp->ExitInstance();
goto InitFailure;
}
ASSERT_VALID(pApp);
nReturnCode = pApp->Run();
ASSERT_VALID(pApp);
InitFailure:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE1("Warning: Temp map lock count non-zero (%ld)./n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
应用程序执行时,Windows自动调用应用程序框架内部的WinMain函数。如清单3-1所示,WinMain函数会查找该应用程序的一个全局构造对象,这个对象是由CWinApp派生类构造的,有且只有一个。它是一个全局对象,因此在程序启动时,它就已经被构造好了。
随后,WinMain将调用这个对象的InitApplication和InitInstance成员函数,完成应用程序实例的初
始化工作。随后,WinMain调用Run成员函数,运行应用程序的消息循环。在程序结束时,WinMain调用AfxWinTerm函数,做一些清理工作。3.4.2 应用程序类 每个应用程序必须从CWinApp派生出自己的应用程序类,并定义一个全局的对象。该应用程序类包含了Windows下应用程序的初始化、运行和结束过程。基于框架建立的应用程序必须有一个(且只能有一个)从CWinApp派生的类的对象。在Hello程序中,我们从CWinApp中派生出一个CHelloApp类,并定义了一个全局对象theApp。CHelloApp类在hello.cpp中定义。
要访问应用程序类构造的对象,可以调用全局函数AfxGetApp()。AfxGetApp()返回一个指向全局对象的指针。可以通过对它进行强制类型转换,转换为我们派生的应用程序类。
比如:
CHelloApp* pApp=(CHelloApp*)AfxGetApp();
在CHelloApp应用程序类中,我们还重载了CWinApp的成员函数InitInstance。InitInstance函数主要完成以下工作:设置注册数据库,载入标准设置(最近打开文件列表等)、注册文档模板。其中注册文档模板过程中隐含地创建了主窗口。接着,处理命令行参数,显示窗口,然后返回、进入消息循环。下面的程序清单3.2给出了Hello程序的InitInstance函数代码。
清单3.2 InitInstance函数
// CHelloApp initialization
BOOL CHelloApp::InitInstance()
{
AfxEnableControlContainer();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
// Change the registry key under which our settings are stored.
// You should modify this string to be something appropriate
// such as the name of your company or organization.
SetRegistryKey(_T("Local AppWizard-Generated Applications"));
LoadStdProfileSettings(); // Load standard INI file options (including MRU)
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CHelloView));
AddDocTemplate(pDocTemplate);
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The one and only window has been initialized, so show and update it.
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
在CWinApp的派生类中,必须重载InitInstance函数,因为CWinApp并不知道应用程序需要什么样的窗口,它可以多文档窗口、单文档窗口,也可以是基于对话框的。Run成员函数
WinMain在初始化应用程序实例后,就调用Run函数来处理消息循环。Run成员函数不断执行消息循环,检查消息队列中有没有消息。如果有消息,Run将其派遣,交由框架去处理,然后返回继续消息循环。如果没有消息,Run将调用OnIdle来做用户或框架可能需要在空闲时才做的工作,象后面我们讲到的用户接口更新消息处理等。如果既没有消息要处理,也没有空闲时的处理工作要做,则应用程序将一直等待,直到有事件发生。当应用程序结束时,Run将调用ExitInstance。消息循环的流程图如图3-10所示。
图3-10 Run成员函数的消息循环
关闭应用程序
用户可以通过选择File-Exit菜单或点主窗口的关闭按钮,关闭主框架窗口,来终止应用程序。此时,应用程序类首先删除m_pMainWnd主框架窗口对象,然后退出Run函数,进而退出WinMain,在退出WinMain后删除TheApp对象。
3.5 几种窗口类型
3.5.1 框架窗口
框架窗口为应用程序的用户界面提供结构框架,它是应用程序的主窗口,负责管理其包容的窗口,一个应用程序的最顶层的框架窗口是应用程序启动时创建的第一个窗口。
MFC提供三种类型的框架窗口:单文档窗口,多文档窗口(MDI),对话框。在AppWizard的第一个对话框中,就提供了选项,让用户选择应用程序是基于单文档、多文档还是对话框的。MFC单文档窗口一次只能打开一个文档框架窗口,而MDI应用程序运行时,在应用程序的一个实例中打开多个文档框架窗口,这些窗口称作子窗口(Child Window)。这些文档可以是同一类型的,也可以是不同类型的。如Visual Studio就可以打开资源文件窗口和源程序窗口等不同类型的窗口。此时,激活不同类型的MDI子窗口,菜单也将相应变化。
MFC提供了三个类CFrameWnd、CMDIFrameWnd、CMDIChildWnd和CDialog 分别用于支持单文档窗口、多文档窗口和对话框。
CFrameWnd
用于SDI框架窗口,形成单个文档及其视的边框。框架窗口既是应用程序的主框架窗口,也是当前文档对应的视图的边框。
CMDIFrameWnd
用于MDI应用程序的主框架窗口。主框架窗口是所有MDI文档窗口的容器,并与它们共享菜单条。MDI框架窗口是出现在桌面中的顶层窗口。
CMDIChildWnd
用于在MDI主框架窗口中显示打开的各个文档。每个文档及其视都有一个MDI子框架窗口,子框架窗口包含在MDI主框架窗口中。子框架窗口看起来类似一般的框架边框窗口,但它是包含在主框架窗口中,而不是位于桌面的,并且为主窗口所裁剪。而且MDI子窗口没有自己的菜单,它与主MDI框架窗口共享菜单。
CDialog
对话框是一种特殊类型的窗口,它的边框一般不可以调整,而且内部包含一些控件窗口。有关对话框作为主窗口的技术可以参见下一章。
要生成一个单文档窗口,主窗口就必须从CFrameWnd派生;要生成一个多文档窗口,主窗口就必须从CMDIFrameWnd派生,而且其中的子窗口必须从CMDIChildWnd派生出来;而基于对话框的窗口程序就要从CDialog派生出主窗口类。
子窗口
子窗口就是具有WS_CHILD风格的窗口,且一定有一个父窗口。所有的控件都是子窗口。子窗口可以没有边框。子窗口被完全限制在父窗口内部。
父窗口
父窗口就是拥有子窗口的窗口。
弹出式窗口
具有WS_POPUP风格,它可以没有父窗口。这种窗口几乎什么都没有,可看作一个矩形区域。
3.5.2窗口的创建
窗口的创建分为两步:第一步是用new创建一个C++的窗口对象,但是此时只是初始化窗口的数据成员,并没有真正创建窗口(这一点与一般的对象有所不同)。
//第一步:创建一个C++对象,其中CMainFrame是从CFrameWnd派生的对象。
CMainFrame* pMyFrame=new CMainFrame();//用new操作符创建窗口对象
或
CMainFrame MyFrame;//定义一个窗口对象,自动调用其构造函数
第二步是创建窗口。CFrameWnd的Create成员函数把窗口给做出来,并将其HWND保存在C++对象的公共数据成员m_hWnd中。
//第二步:创建窗口
pMyFrame->Create(NULL,“My Frame Window”);
或
MyFrame.Create(NULL,“My Frame Window”);
Create函数的原形如下:
BOOL Create( LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle = WS_OVERLAPPEDWINDOW, const RECT& rect = rectDefault, CWnd* pParentWnd = NULL, LPCTSTR lpszMenuName = NULL, DWORD dwExStyle = 0, CCreateContext* pContext = NULL );
Create函数第一个参数为窗口注册类名,它指定了窗口的图标和类风格。这里我们使用NULL做为其值,表明使用缺省属性。第二个参数为窗口标题。其余几个参数指定了窗口的风格、大小、父窗口、菜单名等。
这个函数看起来比较复杂,对于CFrameWnd派生出来的窗口,我们可以使用LoadFrame从资源文件中创建窗口,它只需要一个参数。
pMyFrame->LoadFrame(IDR_FRAME);
LoadFrame使用该参数从资源中获取许多默认值,包括主边框窗口的标题、图标、菜单、加速键等。但是,在使用LoadFrame时,必须确保标题字符串、图标、菜单、加速键等资源使用同一个ID标识符(在本例中,我们使用IDR_FRAME)。
提示:在Hello程序的InitInstance中我们看不到创建窗口的过程。实际上,在
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloDoc),
RUNTIME_CLASS(CMainFrame), // main SDI frame window
RUNTIME_CLASS(CHelloView));
AddDocTemplate(pDocTemplate); 程序片段中,我们看到,CSingleDocTemplate构造函数的第二个参数就是IDR_MAINFRAME。在构造函数内部,已经通过调用m_pMainWnd->LoadFrame(IDR_MAINFRAME),完成了应用程序主窗口的创建过程。
在InitInstance中,创建完窗口后,窗口调用ShowWindow成员函数来显示窗口。ShowWindow带一个参数,指示窗口以何种方式显示(最大化、最小化或一般)。缺省方式为SW_SHOW,但实际上我们经常希望应用程序启动时窗口最大化,此时可以将该参数该为SW_SHOWMAXMIZED,即调用
m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);
在MainFrm.cpp中,我们还看到CMainFrame类有一个OnCreate方法。OnCreate成员函数定义如清单3.3。当调用Create或CreateEx时,操作系统会向窗口发送一条WM_CREATE消息。这一函数就是用来响应WM_CREATE消息的。
清单3.3 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_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);
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
在OnCreate函数中,首先调用CFrameWnd的缺省处理方法OnCreate完成窗口创建工作。后面是应用程序主窗口的特定工作,在上面程序中,创建了工具条和状态栏(有关工具条和状态栏编程参见下一章有关内容)。可以在此处加入一些初始化工作,如从INI文件中载入设置,显示Splash Window(启动画面)等。
3.5.3 注册窗口
在传统的Windows C程序中,送给一个窗口的所有消息是在它的窗口函数中处理的。把一个窗口同它的窗口函数联系起来的过程称为注册窗口类。注册窗口包括对窗口指定一个窗口函数(给出窗口函数的指针)以及设定窗口的光标、背景刷子等内容。一个注册窗口类可以被多个窗口共享。注册窗口通过调用API函数RegisterClass来完成。
在MFC下,框架提供了缺省的自动窗口注册过程。框架仍然使用传统的注册类,而且提供了几个标准的注册类,它们在标准的应用程序初始化函数中注册。调用AfxRegisterWndClass全局函数就可以注册附加的窗口类,然后把已经注册的类传给CWnd的Create成员函数。用户可以定制自己的注册过程,以提供一些附加的特性。比如设置窗口的图标、背景、光标等。下面是注册窗口的例子。
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
UINT ClassStyle=CS_VREDRAW|CS_HREDRAW;
cs.style=cs.style&(~FWS_ADDTOTITLE);
cs.lpszClass = AfxRegisterWndClass(ClassStyle,
AfxGetApp()->LoadStandardCursor(IDC_ARROW),
(HBRUSH)(COLOR_WINDOW+1),//for brush
AfxGetApp()->LoadIcon(IDR_MAINFRAME));
return TRUE;
}
注册窗口在CFrameWnd的PreCreateWnd方法中完成。从成员函数名字PreCreateWindow中就可以看出来,注册窗口的工作必须在调用Create函数创建窗口之前完成。其中cs.style=cs.style&(~FWS_ADDTOTITLE)指定窗口标题风格,关闭自动添加文档标题的功能。AfxRegisterWndClass指定窗口使用箭头光标、背景刷子使用比窗口颜色标号大一的颜色、图标使用IDR_MAINFRAME标识符指定的图标(当然也可以使用其它图标)。用上面的程序段替换Hello程序MainFrm.cpp中的PreCreateWindow成员函数定义,并重新编译和运行程序。此时,窗口标题变成了Hello,原来令人讨厌的“Untitled-”没有了,因为窗口风格中关闭自动添加当前文件名的风格。
3.5.4 关闭和销毁窗口
框架窗口不仅维护窗口的创建,还管理着窗口的关闭和销毁过程。关闭窗口时,操作系统依次向被关闭的窗口发送WM_CLOSE和WM_DESTROY消息。WM_CLOSE消息的缺省处理函数OnClose将调用DestroyWindow,来销毁窗口;最后,框架调用窗口的析构函数作清理工作并删除C++窗口对象。
不要使用C++的delete操作符来销毁框架窗口,而应当采用CWnd的DestroyWindow成员函数来销毁。DestroyWindow首先删除子窗口,再删除窗口本身。若窗口以变量方式产生(即在堆栈上分配内存),该窗口对象会被自动清除。若对象是用new操作符创建的(也就是在堆上分配内存的),则需要用户自己处理。有关DestroyWindow问题在第五章对话框技术中还要作进一步解释。
OnClose()常用功能:保存窗口的一些状态、工具条状态,提示保存未保存的数据等等。
void CMainFrame::OnClose()
{
SaveBarState( "MyDockState" );//保存工具条状态
CFrameWnd::OnClose();
}
3.5.5 窗口激活
活动窗口必定是一个没有父窗口的顶层窗口,包括框架窗口和对话框。当顶层窗口被激活时,Windows向窗口发送WM_ACTIVATE消息,对此消息的缺省处理是将活动窗口设为有输入焦点。
输入焦点用于表示哪个窗口有资格接收键盘输入消息。带有输入焦点的窗口或是一个活动窗口,或者是该活动窗口的子窗口。当一个顶层窗口获得输入焦点时,Windows向该窗口发送WM_SETFOCUS消息,此窗口可将输入焦点重定位到它的子窗口上。子窗口不会自动获得输入焦点。失去输入焦点的窗口会收到WM_KILLFOCUS消息。当子窗口拥有输入焦点时,父窗口就不会处理键盘输入了。
3.6 使用菜单
现在我们要在主窗口中加入自己的菜单。菜单编程一般分三步:
1.编辑菜单资源,设置菜单属性(包括菜单名和ID);
2.用ClassWizard自动映射菜单消息和成员函数;
3.手工编辑成员函数,加入菜单消息处理代码。
3.6.1 编辑菜单资源
仍然使用我们前面生成的Hello程序,编辑由AppWizard自动生成的菜单资源。要编辑菜单资源:
(1) 选择项目工作区的ResourceView标签,切换到资源视图。
(2) 选择菜单资源类型。
(3) 选定菜单资源IDR_MAINFRAME,双击该项或单击鼠标右键然后在弹出菜单中选择Open选项。Visual Studio将弹出菜单编辑窗口,显示菜单资源IDR_MAINFRAME,其中IDR_MAINFRAME是由AppWizard在创建该程序时自动生成的。
(4) 编辑当前菜单
要删除某个菜单项或弹出菜单,可用鼠标单击该菜单或用上下光标键来回选择,然后按Del键删除;要插入新菜单项,可选定窗口中的空白菜单框后按回车(或直接用鼠标双击该空白框),Visual Studio弹出Properties(属性)对话框,如图3-11所示。属性对话框用于输入菜单项的标题、标识符、菜单项在状态栏上显示的提示(Prompt),并为该菜单提供属性调整。也可以在选择一个已有的菜单项时按Ins键,以在该菜单项上方插入一个空白菜单项,然后双击该菜单项进行编辑。要插入一个分隔线,只需将菜单项的Seperator属性打开即可。
Visual Studio支持鼠标拖曳调整菜单项位置。要调整菜单项位置,只需要选中某菜单项并将其拖至适当位置即可。
图3.11 菜单编辑器和属性对话框
如上图,我们首先在Edit和View之间加入一个弹出菜单:用鼠标单击View菜单,按Ins键插入一个空白菜单项,双击该空白框弹出其属性对话框。在菜单属性栏输入“&Test”字样。然后在Test弹出菜单下加入以下四个菜单项,并在&Say Hello菜单下加入一个分隔符。菜单项属性设置如下表:
菜单名 | 菜单ID | 菜单提示(Prompt) |
Say &Hello | ID_SAY_HELLO | Say hello to you! |
&Red | ID_SELECT_RED | The color is red. |
&Blue | ID_SELECT_BLUE | The color is blue. |
&Yellow | ID_SELECT_YELLOW | The color is yellow. |
提示:如果菜单中要使用中文,则除了在菜单名一项中输入中文外,还要将菜单资源的语言属性设置为中文。方法是:鼠标右键单击资源视图的菜单资源IDR_MAINFRAME,弹出快捷菜单,选择Properties,弹出整个菜单资源的属性对话框,如图3-12所示。在Languages下拉列表框中选择Chinese (P.R.C.)。这样以后菜单就可以正确使用和显示中文了。如果其他资源如对话框或字符串要使用中文,也要将该资源的语言属性改为Chinese(P.R.C)。 |
图3.12 设置菜单语言属性
现在关闭菜单编辑器窗口。我们要为Say Hello菜单增加一个加速键CTRL+H。要编辑加速键,选择Accelerator资源类型,双击打开IDR_MAINFRAME加速键资源。要删除加速键,可以直接按Del键。要增加加速键,可以按Ins键,弹出加速键属性对话框。在ID下拉列表框中选择ID_SAY_HELLO,在Key一栏中输入H,完成加速键设置。关闭加速键编辑窗口。
3.6.2 用ClassWizard自动映射菜单消息和成员函数
现在我们用ClassWizard为上面创建的几个菜单生成和映射消息处理成员函数。在此之前我们首先介绍一下ClassWizard的用法。
用ClassWizard管理类和Windows消息
ClassWizard有助于创建Windows消息和命令处理函数、创建和管理类、创建类成员变量、创建OLE Automation的方法和属性、创建数据库类以及其他一些工作。
ClassWizard也有助于覆盖MFC类中的虚函数。先选类,再选择需要覆盖的虚函数。该过程的其余部分与消息处理是类似的。启动ClassWizard应用程序
从View菜单或源程序编辑窗口右键菜单中选择ClassWizard(快捷键:Ctrl +W),Developer Studio将弹出MFC ClassWizard对话框。该对话框包含几个标签页,提供以下选项:
Message Maps:它管理消息和成员函数之间的映射关系。
Member Variables:它可以让用户加进一些数据成员,以便和各种控制进行数据交换。
Automations:它提供了各种特性支持OLE2.0,包括为OLE Automation增加属性、方法以及处理事件。
ActiveX Events:为ActiveX控件增加属性、方法以及为ActiveX控件事件增加处理函数。
Class Info:它可以让用户创建新类,以便支持对话框和各种可视类(包括控制、窗口等)。还可以从类库文件导入类到当前工程中。
在这一章里,我们只用到Message Maps这一页,因此我们在这里只介绍Message Maps的使用。
Message Maps选项
可以让用户加入成员函数来处理消息,删除成员函数以及对成员函数进行编辑。
Message Maps页包括如下控制选项:
Projects组合框:允许用户选择当前工作区中包含的工程。Class Name组合框:允许用户选择当前工程中的类。Objects IDs列表框:列出当前选中的类名及相关的ID。对窗口和视来说,这些ID为菜单标识符;对对话框来说,这些ID为控制框的ID。Messages列表框:列出当前所选类的可重载的虚方法以及可接收到的消息。Member Functions列表框:列出ClassName组合框中当前所选的项中所包含的所有成员函数。用户可以增加、删除成员函数,也可以对成员函数进行编辑。Add Class...按钮:它允许用户往工程里添加新类。在按钮右边有一个向下的小箭头,表明按此按钮将弹出一个菜单。菜单包含两项:New...可以新建一个类;From a type Lib用于从一个类库中导入类。Add Function按钮:它允许用户往Member Functions列表框中加进一个新的消息处理成员函数,该新增成员函数被用来响应Message Maps列表中当前所选中的消息。Delete Function按钮:用于删除Member Functions列表框中所选中的项。Edit Code按钮:它允许用户对Member Functions中所选中的项进行编辑,此时Visual Studio 将关闭MFC ClassWizard对话框,并打开相应文件,并将光标定位在函数定义的开头处。
图3-13 用ClassWizard增加菜单消息成员函数映射
现在我们就利用ClassWizard为Hello程序增加菜单消息和成员函数的映射。在View菜单下选择ClassWizard,弹出MFC ClassWizard对话框。选择Message Maps页,在Class Name下拉列表中选择CMainFrame类。在Object IDs中选择ID_SAY_HELLO,在Messages栏中双击COMMAND,弹出Add Member Function对话框。对话框中给出缺省的成员函数OnSayHello,按OK接收缺省的成员函数名。此时OnSayHello 成员函数就出现在Member Functions列表框中,后面跟所映射的消息,如图3-13所示。列表框中开头的字母W表示窗口消息,V表示可重载的虚方法(Virtual Method)。如此,依次为ID_SELECT_BLUE、ID_SELECT_RED、ID_SELECT_YELLOW增加消息处理成员函数OnSelectBlue、OnSelectRed、OnSelectYellow。然后双击Member Functions列表中的OnSayHello,编辑OnSayHello成员函数。
3.6.3 手工添加代码
在OnSayHello成员函数体中加入语句。
void CMainFrame::OnSayHello()
{
AfxMessageBox(“Hello!”);
}
消息框函数
AfxMessageBox用来弹出一个消息框,它的函数原型有两种版本:
int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );
int AFXAPI AfxMessageBox( UINT nIDPrompt, UINT nType = MB_OK, UINT nIDHelp = (UINT) –1 );
在第一种形式中,lpszText表示在消息框内部显示的文本,消息框的标题为应用程序的可执行文件名(如Hello)。在第二种形式中,nIDPrompt为要显示的文本字符串在字符串表中的ID。函数调用时会自动从字符串表中载入字符串并显示在消息框中。nType为消息框中显示的按钮风格和图标风格的组合,可以采用|(或)操作符组合各种风格。
按钮风格
MB_ABORTRETRYIGNORE 消息框中显示Abort、Retry、Ignore按钮MB_OK 显示OK按钮MB_OKCANCEL 显示OK、Cancel按钮MB_RETRYCANCEL 显示Retry、Cancel按钮MB_YESNO 显示Yes、No按钮MB_YESNOCANCEL 显示Yes、No、Cancel按钮
图标风格MB_ICONINFORMATION 显示一个i图标,表示提示MB_ICONEXCLAMATION 显示一个惊叹号,表示警告MB_ICONSTOP 显示手形图标,表示警告或严重错误MB_ICONQUESTION 显示问号图标,表示疑问
比如,要在消息框中显示一个问号、一个“Yes”按钮、一个“No”按钮,可以以下面的方式调用AfxMessageBox。
AfxMessageBox(“Are you sure?”,MB_YESNO|MB_ICONQUESTION);
还有一个与AfxMessageBox类似的函数MessageBox,它是CWnd的类成员函数:
int MessageBox( LPCTSTR lpszText,LPCTSTR lpszCaption = NULL,
UINT nType = MB_OK );
与AfxMessageBox不同的是,它多了一个lpszCaption参数,从名字上就可以推断出它表示消息框的标题,这样就可以设置消息框的标题,而不必采用可执行文件名作为标题了。
两个函数的区别:AfxMessageBox比MessageBox简单一些,因为它是一个全局函数所以不需要对应的一个窗口类,但是不能控制消息框标题,常用于调试程序时的内部数据输出或警告;MessageBox比较正式,常用在要提交的应用程序版本中,可以控制标题内容而不必采用含义不明的可执行文件名为标题。
现在再来编写OnSelectRed、OnSelectBlue、OnSelectYellow三个函数。首先我们双击CMainFrame类名,在MainFrm.h中加入数据成员,如下所示:
class CMainFrame:public CFrameWnd{
...
// Attributes
public:
int m_nColor;
enum{RED=0,BLUE=1,YELLOW=2};
...}
注意我们这里使用了匈牙利命名法,建议读者也采用这种命名方法,以便提高程序可读性。加入数据成员后,还要对它进行初始化,初始化工作在CMainFrame()构造函数中完成。
CMainFrame::CMainFrame()
{
m_nColor=RED;
}
OnSelectRed、OnSelectBlue、OnSelectYellow三个函数修改后如清单3.4:
清单3.4
void CMainFrame()::OnSelectRed()
{
m_nColor=RED;
SayColor();
}
void CMainFrame()::OnSelectBlue()
{
m_nColor=BLUE;
SayColor();
}
void CMainFrame()::OnSelectYellow()
{
m_nColor=YELLOW;
SayColor();
}
然后在MainFrm.h中加入SayColor()函数的声明:
//Operations
public:
void SayColor();
在MainFrm.cpp中,在OnSelectYellow()成员函数后面,手工加入SayColor()函数的定义。void CMainFrame::SayColor()
{
switch(m_nColor)
{
case RED:
AfxMessageBox(“Color is red!”);
break;
case BLUE:
AfxMessageBox(“Color is blue!”);
break;
case YELLOW:
AfxMessageBox(“Color is yellow!”);
break;
}
}
这样,当我们选择颜色时,就会显示不同的消息框显示当前选择的颜色。但是用消息框显示当前选中的颜色似乎太繁琐了一些。我们在使用WORD编写文档时,注意到在选择不同的视图时,在视图菜单名前显示一个点,表明这是当前选择的视图。我们是否也可以这么做?回答是肯定的。要实现这一功能,就要使用MFC框架的更新命令用户接口消息机制。
3.7 更新命令用户接口(UI)消息
一般情况下,菜单项和工具条按钮都不止一种状态,我们经常需要根据应用的内部状态来对菜单项和工具条按钮作相应的改变。例如,在我们没有选择任何内容时,编辑菜单下的“复制”、“剪切”等菜单是无效的(灰色显示)。有时,我们还会看到,在菜单项旁边可能还会有检查标记,表示它是选中的还是不选中的。比如,在Word的视图菜单下,根据用户所选的显示模式,在“普通”、“大纲”、“页面”“主控文档”前会出现一个点符号,标识当前所选的视图方式。工具条也有类似的情形,如果按钮不可用也可以被置成无效,或者可以被选中。
如果我们采用SDK来编程,那么我们就要跟踪与这些状态相关的变量所有可能发生变化的地方,并根据可能发生的变化作相应的处理。这样的工作非常复杂且容易遗漏。为此,MFC应用程序框架引入了更新命令用户接口消息来简化这一工作。
在ClassWizard的Message Map页中,如果我们选择一个菜单ID,在Messages列表框中就会出现两项:
COMMAND
UPDATE_COMMAND_UI
其中UPDATE_COMMAND_UI就是更新命令用户接口消息,专门用于处理菜单项和工具条按钮的更新。每一个菜单命令都对应于一个更新命令用户接口消息。可以为更新命令用户接口消息编写消息处理函数来处理用户接口(包括菜单和工具条按钮)的更新。如果一条命令有多个用户接口对象(比如一个菜单项和一个工具条按钮),两者都被发送给同一个处理函数。这样,对于所有等价的用户接口对象来说,可以把用户接口更新代码封装在同一地方。
3.7.1用户接口更新原理
为了理解用户接口更新机制,我们来看一下应用框架是如何实现用户接口更新的。当我们选择Edit菜单时,将产生一条WM_INITMENUPOPUP消息。框架的更新机制将在菜单拉下之前集体更新所有的项,然后再显示该菜单。
为了更新所有的菜单项,应用框架按标准的命令发送路线把该弹出式菜单中的所有菜单项的更新命令都发送出去。通过匹配命令和适当的消息映射条目(形式为ON_UPDATE_COMMAND_UI),并调用相应的更新处理器函数,就可以更新任何菜单项。比如,Edit菜单下有Undo、Cut、Copy、Paste等四个菜单项,就要发送四条用户接口更新命令。如果菜单项的命令ID有一个更新处理器,它就会被调用进行更新;如果不存在,则框架检查该命令ID的处理函数是否存在,并根据需要使菜单有效或无效。
如果在命令发送期间找不到对应于该命令的ON_UPDATE_COMMAND_UI项,那么框架就检查是否存在一个命令的ON_COMMAND项,如果存在,则使该菜单有效,否则就使该菜单无效(灰化)。这种更新机制仅适用于弹出式菜单,对于顶层菜单象File和Edit菜单,就不能使用这种更新机制。
按钮的命令更新机制与菜单的命令接口更新机制类似,只是工具条按钮的命令接口更新在空闲循环时完成。有关工具条按钮的接口更新机制,我们将在下一章“工具条和状态栏”中作详细介绍。3.7.2 用户接口更新机制编程
当框架给处理函数发送更新命令时,它给处理函数传递一个指向CCmdUI对象的指针。这个对象包含了相应的菜单项或工具条按钮的指针。更新处理函数利用该指针调用菜单项或工具条的命令接口函数来更新用户接口对象(包括灰化,使,使能,选中菜单项和工具条按钮等)。下面我们使用前面的例子演示如何使用用户接口更新机制:
1.按Ctrl+W激活ClassWizard,选择Message Map选项页。
2.在Object IDs列表中选择 ID_SELECT_RED,在Messages列表中双击ON_UPDATE _COMMAND_UI条目,弹出Add Member Function对话框,缺省函数名为OnUpdateSelect Red,按OK按钮接收此函数名。OnUpdateSelectRed成员函数名就出现在Member Functions列表中。依次给ID_SELECT_BLUE、ID_SELECT_YELLOW增加OnUpdateSelectBlue和OnUpdateSelectYellow命令接口更新成员函数。
3.现在手工编辑刚才生成的成员函数,修改后形式如清单3.5所示:清单3.5
void CMainFrame::OnUpdateSelectBlue(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_nColor==BLUE);
}
void CMainFrame::OnUpdateSelectRed(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_nColor==RED);
}
void CMainFrame::OnUpdateSelectYellow(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
pCmdUI->SetCheck(m_nColor==YELLOW);
}
现在编译运行Hello程序。当我们打开Test菜单时,如图3-14所示,在Red菜单项前已经有了一个对号。因为前面在CMainFrame构造函数中,我们已经将颜色初始化为RED。如果选择Blue,下次打开Test菜单时,在Blue菜单前就会有一个对号,而Red前面的对号则没有了。
图3-14
类似的,要根据某个状态开关菜单,也可以为菜单生成命令接口更新成员函数。比如,在Edit菜单中,如果当前剪贴板没有内容,Paste(粘贴)菜单应当设为无效,程序可以这么写:
void CMainFrame::OnUpdateEditPaste(CCmdUI* pCmdUI)
{
pCmdUI->Enable(!IsClipboardEmpty());
}
其中IsClipboardEmtpy()是读者自己编写的函数,用于判断剪贴板中是否有内容
3.8 快捷菜单
Windows95和Windows 3.x在界面上的一个重大差别就是Windows95增加了功能强大的右键快捷菜单。在任何一个对象上按鼠标右键,就会弹出一个与所选当前对象相关的菜单,菜单中列出了一组针对当前对象的操作。在Visual Studio中就有大量这样的菜单。比如,在项目工作区中单击右键时弹出菜单,让用户选择Docking View(停泊视图)、Hide(隐藏)和Properties(属性)操作。现在我们来讨论如何使用Visual C++为应用程序增加右键菜单。
这里我们也不是手工编程,而是使用Visual Studio提供的构件工具Component Gallery(组件画廊)向框架窗口添加快捷菜单。有关Componet Gallery的概念参见第二课2.1.5节。选择Project->Add to Project->Component and Controls菜单,弹出Component and Controls Gallery对话框,选择Developer Studio Components目录,在该目录下选择Popup Menu构件,如图3-15所示。
图3-15
按Insert按钮。弹出Poup Menu对话框,在Add popup menu to下拉列表框中选择CMainFrame,点OK按钮,关闭Popup Menu对话框。按Close按钮关闭Component and Controls Gallery对话框。编译运行Hello,弹出窗口后按右键,就弹出如图3-16所示的快捷菜单。菜单中包含三项:cut、copy、paste。因为没有对应的消息矗立函数,所有这些菜单都是灰色的、非活动的。
图 3-16
现在,我们看看Component Gallery是如何实现快捷菜单的。首先看资源视图的菜单资源,Component Gallery在其中增加了一个ID为CG_IDR_POPUP_MAIN_FRAME的菜单,菜单中包含了刚才我们看到的三个菜单项:cut、copy、paste。切换到类视图,浏览CMainFrame类,可以看到CMainFrame增加了一个OnContextMenu的成员函数,它是CWnd的一个方法,用于处理鼠标右键单击消息,原型如下:
afx_msg void OnContextMenu(CWnd* pWnd,CPoint point);
其中pWnd指向右键单击的窗口,它可以是一个本窗口的一个子窗口。比如,我们在工具条上单击右键时也弹出同样的菜单,工具条就是框架窗口的一个子窗口。OnContextMenu函数定义如清单3.6所示。
清单3.6 右键菜单
void CMainFrame::OnContextMenu(CWnd*, CPoint point)
{
// CG: This block was added by the Pop-up Menu component
{
if (point.x == -1 && point.y == -1){
//如果是键盘激活的快捷菜单,则在窗口左上角5,5的位置显示快捷菜单
CRect rect;
GetClientRect(rect);
ClientToScreen(rect);
point = rect.TopLeft();
point.Offset(5, 5);
}
//载入快捷菜单资源
CMenu menu;
VERIFY(menu.LoadMenu(CG_IDR_POPUP_MAIN_FRAME));
//取得本菜单的第一个子菜单
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
CWnd* pWndPopupOwner = this;
//如果当前窗口是一个子窗口,取其父窗口作为弹出菜单的拥有者
while (pWndPopupOwner->GetStyle() & WS_CHILD)
pWndPopupOwner = pWndPopupOwner->GetParent();
//在point.x,point.y处显示弹出式菜单并跟踪其选择项
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y,pWndPopupOwner);
}
}
一般的,我们都可以使用Component Gallery的Popup menu构件为某个窗口、对话框、视图等增加快捷菜单而无需手工编程。我们要做的只是编辑修改缺省的菜单为我们自己的快捷菜单,并用ClassWizard生成必要的成员函数,在加入自己的代码。如果确实要手工做的话,首先应当用菜单编辑器增加一个菜单,然后为对应的窗口添加OnContextMenu方法,OnContextMenu的定义可以参考上面的程序。
Component Gallery的功能远不止向程序添加快捷菜单这一项,它还可以增加启动画面(Splash Window)、多页式对话框、口令检查对话框等多种功能。读者可以试着往Hello程序中添加Splash Window和口令对话框,体验一下Component Gallery的强大功能。
小 结
在这一章里,我们主要向读者介绍:
如何使用AppWizard生成Hello框架程序,并手工修改代码,让窗口显示 “Hello,World!”,并介绍了AppWizard所生成的文件。
应用程序的执行机制:框架调用缺省的WinMain函数,首先执行InitInstance初始化应用程序类的一个实例,然后调用Run进入消息循环。
框架窗口的使用:包括窗口的创建、注册类、窗口的关闭和撤消。
在窗口中加入菜单,分为三步工作:第一步,用菜单编辑器编辑菜单;第二步,用ClassWizard生成消息处理函数;第三步,手工编辑消息处理函数,完成特定的功能。
用户接口更新消息:接口更新机制原理,如何控制菜单的使能、灰化、选中。
快捷菜单编程:使用Component Gallery给应用程序添加快捷菜单。