第12单元 文档/视图结构本单元教学目标以单文档界面(SDI)应用程序为例,介绍MFC的文档/视图程序结构。
学习要求理解文档/视图结构,可在AppWizard生成的SDI程序框架的基础上添加必要的代码,以生成自己的应用程序。
授课内容大部分应用程序均要使用数据,其主要工作可以分为两部分:一是对数据的管理,如存储、复制和查询等任务,一是对数据的处理和输入输出,包括显示和打印。MFC提供了“文档/视图”结构支持这类应用程序。
12.1文档/视图概念在文档/视图结构里,文档可视为一个应用程序的数据元素的集合,MFC通过文档类提供了大量管理和维护数据的手段。视图是数据的用户界面,可将文档的部分或全部内容在其窗口中显示,或者通过打印机打印出来。视图还可提供用户与文档中数据的交互功能,将用户的输入转化为对数据的操作。
在MFC中,有两种类型的文档视结构,即单文档界面(SDI:Single Document Interface)应用程序和多文档界面(MDI:Multiple Document Interface)应用程序。
在单文档界面(SDI)程序中,用户在同一时刻只能操作一个文档,例如Windows的NotePad(记事本)程序采用的就是单文档界面。在SDI程序中,打开新文档时会自动关闭当前打开的活动文档,如果当前文档修改后尚未保存,会提示用户是否保存所做的修改。SDI应用程序一般都提供一个File菜单,在该菜单下有一组命令,用于新建文档(New)、打开已有文档(Open)、保存或换名存盘文档等。这类程序相对比较简单,常见的应用程序有终端仿真程序和一些工具程序。
多文档界面(MDI)应用程序允许同时对多个文档进行操作,例如Microsoft Word和Developer Studio本身采用的都是多文档界面。在MDI程序中可以打开多个文档(同时也为每个文档打开一个窗口),可以通过切换活动窗口激活相应的文档进行编辑处理。MDI应用程序也提供一个File菜单,用于新建、打开和保存文档。与SDI应用程序不同的是,MDI的File菜单中还有一个Close(关闭)菜单选项,用于关闭当前打开的文档。MDI应用程序还提供一个窗口菜单,管理所有打开的子窗口,包括对子窗口的新建、关闭、层叠、平铺等。关闭一个窗口时,窗口内的文档也被自动关闭。在本单元中,我们只讨论SDI应用程序的结构,MDI应用程序在第16单元中介绍。
文档/视图结构大大简化了多数应用程序的设计开发过程。其特点主要有:
1.将对数据的操作与数据显示界面分离,放在不同类的对象中处理。这种思想使得程序模块的划分更加合理。文档对象只负责数据的管理,不涉及用户界面;视图对象只负责数据输出和与用户的交互,可以不考虑数据的具体组织结构的细节。
2.MFC在文档/视图结构中提供了许多标准的操作界面,包括新建文件、打开文件、保存文件、文档打印等,大大减轻了程序员的工作量。程序员不必再书写这些标准处理的代码,从而可以把更多的精力放到完成应用程序特定功能的代码上。
3.支持打印、打印预览和电子邮件发送功能。程序员只需要编写很少的代码甚至根本无需编写代码,就可以为应用程序提供“所见即所得”式的打印和打印预览这类功能。
4.使用Developer Studio的AppWizard可生成基于文档/视结构的SDI或MDI框架程序,程序员只需在其中添加与特定应用有关的部分代码,就可完成应用程序的开发工作。
然而,文档/视图结构也不是万能的。有两种情况不宜采用文档/视图结构:
1.不是面向数据的应用程序或数据量很少的应用程序,如Windows自带的磁盘扫描程序、时钟程序等工具软件,以及一些过程控制程序等;
2.不使用标准窗口界面的程序,象一些游戏软件等。
12.2文档/视图结构程序实例
[例12-1] 修改例9-1的吹泡泡程序。该程序的功能很简单:用户使用鼠标左键点击窗口客户区,则可生成一圆形泡泡(其半径由一随机数确定)。这样生成的所有泡泡的位置和大小就构成了该程序的文档,可以存放在磁盘上,也可以重新打开并修改。窗口客户区的泡泡图象还可以通过打印机输出。
说 明:使用AppWizard建立一个Win32应用程序空项目,并通过Developer Studio主菜单的Project/Settings…选项,在Project Settings对话框的General选项卡中设置使用MFC(Using MFC in a shared DLL)。然后为项目建立一个空源程序文件并输入后面的源程序。另外,还需通过菜单选项“File/New…”调出New对话框为项目建立一个资源文件(Resource Script)。使用菜单选项“File/Close”将刚建立的资源文件关闭,然后使用菜单选项“File/Open”调出打开文件对话框,在其中选择资源文件×××.rc(×××为资源文件名),并在对话框底部的Open As组合框中选择Text(以文本方式打开),按下“打开(O)”按钮以文本方式重新打开资源文件。将原来的所有内容删除,替换为:
#include "afxres.h"
#define IDR_MAINFRAME 128
IDR_MAINFRAME MENU PRELOAD DISCARDABLE
BEGIN
POPUP "文件(&F)"
BEGIN
MENUITEM "新建(&N)\tCtrl+N",ID_FILE_NEW
MENUITEM "打开(&O)...\tCtrl+O",ID_FILE_OPEN
MENUITEM "保存(&S)\tCtrl+S",ID_FILE_SAVE
MENUITEM "另存为(&A)...",ID_FILE_SAVE_AS
MENUITEM SEPARATOR
MENUITEM "打印(&P)...\tCtrl+P",ID_FILE_PRINT
MENUITEM "打印预览(&V)",ID_FILE_PRINT_PREVIEW
MENUITEM "打印设置(&R)...",ID_FILE_PRINT_SETUP
MENUITEM SEPARATOR
MENUITEM "退出(&X)",ID_APP_EXIT
END
END
STRINGTABLE PRELOAD DISCARDABLE
BEGIN
IDR_MAINFRAME "吹泡泡\n\nBub\nBub 文件 (*.bub)\n.bub"
END
#include "l.chs\\afxres.rc"
#include "l.chs\\afxprint.rc"
然后编译、连接项目。
程 序:
// Example 12-1,吹泡泡(最小文档视图框架) ////////////////////
#include <afxwin.h>
#include <afxext.h>
#include <afxtempl.h>
// 文档类 ////////////////////////////////////////////////////
class CMyDoc,public CDocument
{
DECLARE_DYNCREATE(CMyDoc)
CArray <CRect,CRect&> m_rectBubble; // 泡泡数组
public:
CMyDoc();
int GetListSize(){return m_rectBubble.GetSize();}
CRect GetBubble(int index){return m_rectBubble[index];}
void AddBubble(CRect rect){m_rectBubble.Add(rect);}
virtual BOOL OnNewDocument();
virtual void DeleteContents();
virtual void Serialize(CArchive& ar);
};
IMPLEMENT_DYNCREATE(CMyDoc,CDocument)
// 构造函数:对SDI仅调用一次,做初始化工作
CMyDoc::CMyDoc()
{
m_rectBubble.SetSize(256,256); // 设置数组参数
}
// 打开新文档:每次打开新文档时调用,做某些初始化工作
BOOL CMyDoc::OnNewDocument()
{
if (!CDocument::OnNewDocument())
return FALSE;
srand((unsigned)time(NULL)); // 初始化随机数发生器
return TRUE;
}
// 清理文档:关闭文档、建立新文档和打开文档前调用
void CMyDoc::DeleteContents()
{
m_rectBubble.RemoveAll(); // 泡泡数组清零
CDocument::DeleteContents();
}
// 系列化:读写文档时自动调用
void CMyDoc::Serialize(CArchive &ar)
{
m_rectBubble.Serialize(ar);
}
// 视图类 ///////////////////////////////////////////////////
class CMyView,public CView
{
DECLARE_DYNCREATE(CMyView)
public:
CMyDoc* GetDocument(){return (CMyDoc*)m_pDocument;}
virtual void OnInitialUpdate();
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnDraw(CDC* pDC);
afx_msg void OnLButtonDown(UINT nFlags,CPoint point);
DECLARE_MESSAGE_MAP()
};
IMPLEMENT_DYNCREATE(CMyView,CView)
BEGIN_MESSAGE_MAP(CMyView,CView)
ON_WM_LBUTTONDOWN()
ON_COMMAND(ID_FILE_PRINT,CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT,CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW,CView::OnFilePrintPreview)
END_MESSAGE_MAP()
// 更新初始化:当建立新文档或打开文档时调用
void CMyView::OnInitialUpdate()
{
CView::OnInitialUpdate();
Invalidate(); // 更新视图
}
// 绘制视图:程序开始运行或窗体发生变化时自动调用
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument(); // 取文档指针
ASSERT_VALID(pDoc);
pDC->SelectStockObject(LTGRAY_BRUSH); // 在视图上显示文档数据
for(int i=0; i<pDoc->GetListSize(); i++)
pDC->Ellipse(pDoc->GetBubble(i));
}
// 消息响应:用户点击鼠标左键时调用
void CMyView::OnLButtonDown(UINT nFlags,CPoint point)
{
CMyDoc* pDoc = GetDocument(); // 取文档指针
ASSERT_VALID(pDoc);
int r = rand()%50+5; // 生成泡泡半径
CRect rectBubble(point.x-r,point.y-r,point.x+r,point.y+r);
pDoc->AddBubble(rectBubble); // 修改文档数据
pDoc->SetModifiedFlag(); // 设置修改标志
InvalidateRect(rectBubble,FALSE); // 更新视图
}
// 准备打印:设置打印参数
BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo)
{
pInfo->SetMaxPage(1); // 设置打印页数
return DoPreparePrinting(pInfo);
}
// 主框架类 //////////////////////////////////////////////////
class CMainFrame,public CFrameWnd
{
DECLARE_DYNCREATE(CMainFrame)
};
IMPLEMENT_DYNCREATE(CMainFrame,CFrameWnd)
// 应用程序类 ///////////////////////////////////////////////
#define IDR_MAINFRAME 128 // 主框架的资源代号
class CMyApp,public CWinApp
{
public:
virtual BOOL InitInstance();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyApp,CWinApp)
ON_COMMAND(ID_FILE_NEW,CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN,CWinApp::OnFileOpen)
ON_COMMAND(ID_FILE_PRINT_SETUP,CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
// 初始化程序实例:建立并登记文档模板
BOOL CMyApp::InitInstance()
{
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate( // 登记文档模板
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CMyView));
AddDocTemplate(pDocTemplate);
CCommandLineInfo cmdInfo; // 创建及处理命令行信息 SDI
ParseCommandLine(cmdInfo);
if (!ProcessShellCommand(cmdInfo))
return FALSE;
m_pMainWnd->ShowWindow(SW_SHOW); // 初始化框架窗口
m_pMainWnd->UpdateWindow();
return TRUE;
}
// 全局应用程序对象
CMyApp theApp;
图12-1 SDI结构的吹泡泡程序
输入输出:用鼠标左键可在窗口生成一系列泡泡。程序的框架窗口附有一个文件菜单,其中包括“新建”、“打开”、“保存”和“打印”等文档操作选项,如图12-1所示。
分 析:在本程序中一共用到4个类,即CMyApp、CMyWnd、CMyView和CMyDoc,它们分别是应用程序类CWinApp、框架窗口类CFrameWnd、视图类CView和文档类CDocument的派生类。
文档派生类CMyDoc用于管理吹泡泡程序的数据,即所有泡泡的包含矩形。由于我们无法断定用户会生成多少泡泡,所以声明了一个数组m_rectBubble来存放所有泡泡的包含矩形。m_rectBubble是CMyDoc类的私有数据成员,在类外不可见;所以要为其定义一组成员函数作为该数据成员与外界的接口。函数 GetListSize()用来统计数组中泡泡的个数;函数GetBubble()用于从数组中取一个泡泡的包含矩形;函数AddBubble()用于将一个泡泡的包含矩形加入数组。由于这些成员函数都比较简单,所以采用内联函数的形式。
视图派生类的OnLButtonDown()函数用于响应用户的鼠标消息,OnDraw()函数用于在视图上输出(当然也是在框架窗口的客户区输出)。这些原来都是框架窗口类的工作,在文档/视图结构中转移到视图类来了。
由于数据放到了文档类中,消息响应和输出工作放到了视图类中,因此框架窗口类就显得非常简洁了。
应用程序类与以前不同的是增加了三个消息响应宏,用于处理文件菜单的选项。由于这些工作都是标准化的,所以无需重载消息响应函数。
在应用程序类的InitInstance()函数中建立了一个文档模板。
在以下几节中,将根据本例详细说明文档/视图结构的程序构造。
12.3 文档/视图结构中的应用程序类应用程序类负责唯一的全局应用程序对象的创建、初始化、运行和退出清理过程,这个过程我们已经很熟悉了。
对于文档/视图结构,要在应用程序类的InitInstance()函数中创建一个文档模板,来管理文档/视图结构涉及的框架窗口、文档和视图。
文档模板负责在运行时创建(动态创建)文档、视图和框架窗口。一个应用程序对象可以管理一个或多个文档模板,每个文档模板用于动态创建和管理一个或多个同类型的文档(这取决于应用程序是SDI程序还是MDI程序)。MFC的文档模板类CDocTemplate用于支持文档模板操作。由于文档模板类是一个抽象基类,因此不能直接用其声明对象,只能使用其派生类。对于单文档界面程序,应使用CSingleDocTemplate(单文档模板类),对于一个多文档界面程序,使用CMultipleDocTemplate(多文档模板类)。例12-1的吹泡泡程序是单文档界面程序,所以使用单文档界面模板:
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CMyView));
文档模板定义了文档、视图和框架窗口这三个类的关系。通过文档模板,可以知道在创建或打开一个文档时,需要用什么样的视图和框架窗口显示它。这是因为文档模板保存了文档及其对应视图和框架窗口的CRuntimeClass对象的指针。此外,文档模板还保存了所支持的全部文档类的信息,包括这些文档的文件扩展名信息、文档在框架窗口中的名字、代表文档的图标等信息。
由于文档模板要在运行时动态创建相应的框架程序对象、文档对象和视图对象,因此要求程序中使用的上述类支持动态创建功能。通过CMyWnd、CMyView和CMyDoc类中的DECLARE_DYNCREATE()宏和程序中的IMPLEMENT_DYNCREATE()将它们说明为动态创建类,文档模板即可动态创建这些类的对象。
在CSingleDocTemplate类的构造函数中,还有一个IDR_MAINFRAME参数,用于标明资源文件中的某些资源。本程序共使用了两项资源,一个文件菜单和一个字符串(应用程序名)。
在创建了文档模板之后,InitInstance()函数调用AddDocTemplate()函数将创建好的文档模板加入到应用程序的可用文档模板链表中去。这样,如果用户选择了File/New或File/Open菜单选项要求创建或打开一个文档时,应用程序类的OnNewDocument()成员函数和OnOpenDocument()成员函数就可以从文档模板链表中检索出相应的文档模板提示用户选择适当的文档类型并创建文档及其相关的视图和框架窗口。
应用程序类的消息映射
BEGIN_MESSAGE_MAP(CMyApp,CWinApp)
ON_COMMAND(ID_FILE_NEW,CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN,CWinApp::OnFileOpen)
END_MESSAGE_MAP()
用于响应新立文档和打开文档消息,这些消息在用户选择文件处理菜单的有关选项时送出。相应的的消息处理函数OnNewDocument()和OnOpenDocument()中已经包含了有关新建文档和打开文档的全部标准代码,因此不必再重载这些函数并添加代码了。
在创建了文档模板并将其加入应用程序的可用文档模板链表之后,还要创建及处理命令行信息:
CCommandLineInfo cmdInfo;
ParseCommandLine (cmdInfo);
if (!ProcessShellCommand (cmdInfo))
return FALSE;
如果用命令行方式运行应用程序,则可使用命令行参数,通常为文档文件名。这时应用程序会自动打开该文档;如果不使用命令行参数,则应用程序自动建立一个新文档。
在应用程序类的InitInstance()函数的最后初始化并显示框架窗口。
12.4 框架窗口类与基于框架窗口的应用程序相比,文档/视图结构程序的框架窗口类显得十分简单。在文档/视图结构中,视图在框架窗口中显示,是框架窗口的子窗口并完全覆盖框架窗口的客户区。框架窗口的作用有二:一是为视图提供可视的边框,包括标题条、一些标准的窗口组件(最大、最小化按钮、关闭按钮),象一个容器一样把视图包装起来。二是响应标准的窗口消息,包括最大化、最小化、调整尺寸等。当框架窗口关闭时,其中的视图类对象也被自动删除。
为简洁起见,例12-1的吹泡泡程序的框架窗口派生类省略了几乎所有可选内容,包括创建状态栏和工具栏的工作。
12.5 视图类视图类CView是窗口类CWnd类的派生类。视图类对象完全覆盖框架窗口的用户区,没有自己的边框。视图规定了用户查看文档数据以及同数据交互的方式。
视图类有几个重要的成员函数。
GetDocument()成员函数用于从文档类中获取数据值。实际上,该函数提供一个指向文档派生类对象的指针,通过该指针可访问文档派生类的公有成员。例如
GetDocument ( )(>AddBubble ( rectBubble );
正因为如此,文档类的数据成员只能声明为公有的,而不能象面向对象技术所要求的那样,将所有的数据成员均声明为私有成员。由于文档类和视图类的关系十分密切,这样做可以简化程序设计,并不会因封装性被破坏而造成混乱。
在视图类中,用OnDraw()成员函数更新视图,其用法类似窗口类的OnPaint()函数。所不同的是,OnDraw()函数有一个指向CDC类的指针参数,通过该参数可以直接向视图输出。
应用程序框架调用视图的CView::OnDraw(CDC* pDC)方法完成屏幕显示、打印和打印预览功能,对于不同的输出功能会传递不同的DC指针给OnDraw()函数。
在OnDraw()函数中,首先调用GetDocument()函数,取得指向当前视图所对应的文档的指针。然后通过这个指针来访问文档中的数据。
在绘图时,可以通过传给OnDraw()函数的设备上下文指针pDC进行GDI调用。开始绘图之前,往往需要选择GDI资源(或GDI对象,包括画笔、刷子、字体等),将其选入设备环境。绘图代码是与设备无关的,也就是说在编写这些代码时并不需要知道目前使用的是什么设备(屏幕、打印机或其他绘图设备)。
视图类的OnInitialUpdate()虚成员函数在应用程序启动,或用户从File菜单中选择了New或者Open选项时被调用。因此,这是添加某些与文档显示有关的初始化工作的有关代码的好地方。重载该虚函数时要注意确保调用了基类CView的OnInitialUpdate()成员函数。
12.6 文档类文档类CDocument的派生类对象规定了应用程序的数据。
当用户启动应用程序,或从应用程序的File菜单中选择New选项时,都需要对文档类的数据成员进行初始化。我们知道,一般类的数据成员的初始化工作都是在构造函数中完成的,在构造函数调用结束时对象才真正存在。但对于文档类来说却不同,文档类的数据成员初始化工作是在OnNewDocument()成员函数中完成的,此时文档对象已经存在。为什么呢?这是因为,在单文档界面(SDI)应用程序中,应用程序启动时,文档对象就已经被创建,直到主框架窗口被关闭时才被销毁。在用户选择File/New菜单选项时,应用程序对象并不是销毁原来的文档对象然后重建新的文档对象,而只是重新初始化(Re-Initialization)文档对象的数据成员,这个初始化工作就是由应用程序对象的OnFileNew()消息处理成员函数通过调用OnNewDocument()成员函数来完成的。试想,如果把初始化数据成员的工作放在构造函数中,由于对象已经存在,构造函数无法被调用,也就无法完成初始化数据成员的工作。为了避免重复代码,在应用程序启动时,应用程序对象也是通过调用OnNewDocument()成员函数来初始化文档对象的数据成员。
在关闭应用程序并删除文档对象时,或用File/Open菜单选项打开一个文档时,需要清理文档中的数据。同文档的初始化一样,文档的清理也不是在文档的析构函数中完成,而是在文档类的DeleteContents()成员函数中完成的。文档类的析构函数只用于清除那些在对象生存期都将存在的数据项(如使用new运算符生成的数据)。DeleteContents()成员函数的调用有两个作用:
1.删除文档的数据;
2.确信一个文档在使用前为空。
注意,OnNewDocument()函数也会调用DeleteContents()函数。在用户选择File/Open菜单选项时,应用程序对象调用应用程序类的OnFileOpen()成员函数,提示用户输入文件名,然后调用CWinApp::OpenDocumentFile()函数打开一个文件。OpenDocumentFile()函数在打开文件后首先调用DeleteContents()成员函数清理文档中的数据,确保消除以前打开的文档的数据被清理掉。
缺省的DeleteContents()函数什么也不做。在编写应用程序时,需要重载DeleteContents()函数并编写自己的文档清理代码。
文档类的OnOpenDocument()成员函数在调用DeleteContents()函数后,将文档修改标记设置为FALSE(未修改),然后调用Serialize()进行文档的串行化工作。
文档类的OnNewDocument()成员函数首先调用DeleteContents(),并将文档修改标志改为FALSE(关闭窗口时将根据文档修改标志决定是否提示用户保存文档)。
文档类的成员函数 SetModifiedFlag()的作用是设置数据修改标志,其原型为
void SetModifiedFlag ( BOOL bModified = TRUE );
其中参数bModified用于说明数据是否被修改(缺省值为数据被修改)。一旦设置将数据修改标志设置为被修改,则在打开另一个文件或结束程序运行时会弹出一个对话框,提醒用户是否保存文档。该函数通常是在重载的视图类消息处理成员函数中通过文档指针调用。
12.7 文档/视图结构中各类对象之间的协作关系在应用程序运行过程中,以上几种类型的对象相互协作处理菜单命令和消息。唯一的一个应用程序类对象管理一个或多个文档模板,每个文档模板创建和管理一个(SDI)或多个文档(MDI)。用户通过包含在框架窗口中的视图来浏览和操作文档中的数据。
自学内容
12.8 集合类集合类是用来容纳和处理一组对象或变量的MFC类,每个集合类对象可以看作一个单独的对象。集合类的成员函数可作用于集合的所有元素。MFC提供两种类型的集合类,即基于模板的集合类和非基于模板的集合类。无论是否基于模板,MFC的集合类均为CObject派生类,支持序列化和诊断转储等特性。
根据对象在集合中的组织和存储方式,集合类又可分为三种类型:链表、数组和映射。其中数组类提供动态可扩展和压缩的对象数组;链表类提供有序的双向元素链表。链表的优点是可以快速地插入或删除表内的元素,而映射类提供建立以关键字为基础的映射表。
12.8.1 非基于模板的集合类非基于模板的集合类用于描述一组预先定义的数据类型(如CObject类和CString类等)的元素集合。如果要在程序中使用非基于模板的集合类,需在程序中包含头文件afxcoll.h。
MFC的非基于模板的集合类有许多,以下是其中几个比较常用的:
类名 说明
CObArray 动态可扩展CObject指针数组
CPtrArray 动态可扩展void指针数组
CStringArray 动态可扩展CString对象数组
CObList CObject指针双向链表
CPtrList void指针双向链表
CStringList CString指针双向链表
CMapStringToOb 以CString对象为关键字的CObject指针映射下面以CObList为例说明非基于模板集合类的使用方法。对于链表类来说,主要操作有加入元素、删除元素和访问元素(遍历链表)等。注意CObList类的元素是CObject类的指针而不是CObject类的对象(CObArray类和CMapStringToOb类也是如此)。
向链表中插入元素可从表头或表尾进行。在插入元素时,首先要用new生成一个对象,然后将其指针存入链表。例如
CRect *pBubble = new CRect (point.x(r,point.y(r,point.x+r,point.y+r);
m_listBubble.AddHead ((CObject *)pBubble);
要访问链表的成员,可以使用链表类的成员函数GetHeadPosition()取到链表头地址,然后使用GetNext()函数逐个取得链表元素(指向CObject类的指针)。这两个成员函数的原型为:
POSITION GetHeadPosition( ) const;
CObject* GetNext( POSITION& rPosition ) const;
其中POSITION类型的变量用于存放链表元素的地址,空值NULL表示已指向表尾。例如
POSITION pos = m_listBubble.GetHeadPosition( );
while(pos != NULL)
{
CRect *pBubble = (CRect *)m_listBubble.GetNext(pos);
pDC->Ellipse(pBubble);
}
要删除链表的成员,仍可利用成员函数GetHeadPosition()和GetNext()来遍历链表并用delete逐个删除表中元素(指针)指向的CObject派生类的对象,最后调用链表类的成员函数RemoveAll()删除链表中包含的所有指针元素。由于链表中只存储了一些指向CObject类的指针,如果只是简单地使用成员函数RemoveAll()删除链表元素,则这些指针元素所指向的、动态生成的CObject派生类对象所占用的存储空间并没有释放。更糟糕的是,由于指向它们的指针(链表元素)已被删除,以后再想释放这些对象占用的存储空间也不可能了。因此,清理链表的正确方法是:
POSITION pos = m_listBubble.GetHeadPosition();
while(pos != NULL)
{
CRect *pBubble = (CRect *)m_listBubble.GetNext(pos);
delete pBubble;
}
m_listBubble.RemoveAll();
12.8.2 基于模板的集合类如果要描述一组自定义类型(如类和结构体等)的元素集合,可以使用基于模板的集合类,基于模板的集合类也是以数组、链表和映射表三种方式组织集合类的数据结构。使用基于模板的集合类需在程序中包含头文件afxtempl.h。
MFC的基于模板的集合类有:
类名 说明
CArray 用以创建任何类型对象的数组
CList 用以创建任何类型对象的链表
CMap 用以创建任何类型对象的映射
CTypedPtrArray 用以创建CObArray和CPtrArray对象安全类型数组
CTypedPtrList 用以创建CObList和CPtrList对象安全类型链表
CTypedPtrMap 用以创建CObMap和CPtrMap对象安全类型映射下面以CArray为例说明基于模板的集合类的使用方法。CArray是数组类,除了具有一般数组的特性外,最显著的特点是可以动态调整数组的大小。但在调整数组大小时,数组占用的内存块需要重新移动,效率较低。如果不要求调整数组大小,则对数组集合的访问和对第3单元介绍的普通数组的访问一样快。在使用数组之前,最好使用其成员函数SetSize()设置数组的初始大小和每次扩充的尺寸,这样可以避免在向数组添加元素时频繁的再分配内存和拷贝数据。数组类适用于那些需要快速检索、很少需要增加或删除元素的集合。
在声明CArray类的对象时,要说明数组元素的类型和增加元素、访问元素等操作的参数类型:
CArray <CRect,CRect&> m_rectBubble;
模板中的CRect说明了该数组元素的类型,而CRect&说明使用CArray的成员函数对数组进行操作时,对应数组元素的参数类型。
对数组的操作,除了用下标操作符“[ ]”直接引用其元素外,还有向数组中增加元素Add()和从数组中删除元素RemoveAll()等。
12.8.3 映射类映射类以一种字典的方式组织数据。每个元素由一个关键字和一个数值项组成,关键字用作数值项的标识符,在集合中不允许重复,必须是唯一的。如果给出一个关键字,映射类会很快找到对应的数值项。映射查找是以HASH表的方式进行的,因此在映射中查找数值项的速度很快。除了基于模板的映射类外,预定义的映射类能支持CString对象、字、CObject指针和无类型指针。例如,CMapWordToOb类创建一个映射表对象后,就可以用WORD类型的变量作为关键字来寻找对应的CObject指针。映射类最适用于需要根据关键字进行快速检索的场合。
要访问映射中的数据,可以用映射类的成员函数GetStartPosition()定位到映射表开始处,再用成员函数GetNextAssoc()访问映射表中的成员。
要删除映射中的数据,可以使用映射类成员函数GetStartPosition()和GetNextAssoc()遍历映射并用delete删除对象,然后调用成员函数RemoveAll()。
下面是使用CMap模板类的例子:
CMap <CString,LPCSTR,CPerson,CPerson&> myMap;
CPerson person;
LPCSTR lpstrName =,Tom”;
myMap->SetAt(lpstrName,person);
调试技术
12.9 用AppWizard生成文档/视图结构的程序框架
Visual C++最重要的技术之一就是其“向导(Wizard)”。向导在Developer Studio环境下运行,每个向导擅长为一种特殊类型的应用程序建立项目,并在创建新项目时自动生成一个源程序文件,其中包括了许多通用代码。这样,程序员就不必一切从头做起了。Visual C++的向导种类繁多,功能很强,这一点从Developer Studio的File/New菜单调出的New对话框中的Project选项卡上的项目种类之多就可看出。在前面各单元中,使用向导生成过Win32 Console Application(控制台应用程序)和Win32 Application(32位Windows应用程序)的空项目。本单元着重讨论VisualC++的主向导,即AppWizard(应用程序向导)。
AppWizard(应用程序向导)用于为使用MFC的典型C++ Windows应用程序建立项目。AppWizard最主要的用途是创建基于文档/视图结构的应用程序框架,在这种结构中应用程序的数据由文档对象来维护,并通过视图对象提供给用户。除此而外,AppWizard也能用于创建不依赖于文档/视图结构的应用程序,通过对话框来与用户交流。第14单元介绍如何使用AppWizard创建基于对话框的应用程序。
在AppWizard生成的项目中,每个类都有自己的源代码文件和头文件。AppWizard可为各种各样的程序特征提供代码,如:
单文档界面(SDI)、多文档界面(MDI)和对话框界面;
停靠工具栏、状态栏和打印机支持;
带有典型操作命令,如Open、Save、Print、Cut、Copy和Paste的主菜单;
上下文相关帮助;
显示程序信息和MFC图标的About(关于)对话框;
数据库支持;
对复合文档、Automation和ActiveX控件的OLE/ActiveX支持;
对消息应用编程接口(MAPI)和Windows Sockets的支持。
AppWizard仅在建立项目之初运行一次,但在程序开发的过程中,仍可使用其他Visual C++工具,如ClassWizard帮助生成特定的代码。
AppWizard在项目的目录下生成了许多文件,这些文件包含了框架程序的所有的类、全局变量的声明和定义。下面列出AppWizard为一名为Project的项目生成的各种文件:
文件名 说明
Project.dsw 工作区文件,包含当前工作区项目的信息
Project.dsp MFC的项目文件,包含当前项目的设置和文件等信息
Project.mak MFC的项目文件,与NMAKE兼容
Project.clw ClassWizard用来编辑类、消息映射和对话框数据所需信息
Project.h 主应用程序头文件
Project.cpp 主应用程序源文件(应用程序类源代码)
ProjectView.h 视图类头文件
ProjectView.cpp 视图类源代码
ProjectDoc.h 文档类头文件
ProjectDoc.cpp 文档类源代码
MainFrm.h CMainFrame类头文件
MainFrm.cpp CMainFrame类源代码
StdAfx.h 头文件
StdAfx.cpp 由于MFC体系结构非常大,包含许多头文件,如果每次都编译则比较费时。因此将常用的MFC头文件都放在stdafx.h中,如afxwin.h、afxext.h、afxdisp.h、afxcmn.h等,然后让stdafx.cpp包含这个stdafx.h文件。这样,由于编译器可以识别哪些文件已经编译过,所以stdafx.cpp就只编译一次,并生成所谓的预编译头文件project.pch(因为它存放的是头文件编译后的信息,故名)和预定义的类型文件StdAfx.obj。采用预编译头文件可以加速编译过程
Project.rc 包含项目的资源数据(在第13单元中讨论)
Project.rc2 用于存放Visual Studio不可直接编辑的资源
Resource.h 头文件,包含#define语句来为项目声明常量初次使用AppWizard时可能不很习惯,面对众多的文件和大量代码(其中有些部分甚至显得相当奇怪)不知所措。然而,AppWizard的威力是巨大的。在很短的时间内,程序员就可完成一个开发项目的单调乏味的常规代码部分,直接进入关键代码的开发阶段。AppWizard生成的源代码保证是没有错误的,且可与ClassWizard和各种资源编辑器充分配合。通过用AppWizard来建立项目,可以节省大量时间。
通过Developer Studio的“File/New…”菜单选项可用AppWizard建立一个项目:
选择菜单选项File/New…,在弹出的对话框中选择Project选项卡,其中列出了Visual C++中所有的向导。要创建一个MFC应用程序项目,可选择标有MFC AppWizard(exe)的图标。
图12-2 使用AppWizard建立项目在对话框左方的Project Name文本框中输入项目名称。AppWizard用项目名来构造项目中的文件和MFC派生类的类名,所以项目名不应太长。项目一旦建立后,就没有办法改变项目名了。默认情况下,Visual C++把AppWizard生成的项目放在Common\MsDev98\ MyProjects目录下;如果想使用其他目录,可在Location(路径)文本框中另外指定一个路径。做好以上工作后,单击OK按钮时,正式启动AppWizard创建一个应用项目。见图12-2。
AppWizard的创建过程共有6个步骤,在每个步骤中,对话框的左边均会显示一个图片,给出各项设置的形象提示。在任一步骤中,单击Finish按钮可结束创建工作,接受余下步骤中的默认设置。单击Next或Back按钮可回到上一步骤或进入下一步骤。
步骤1:确定程序类型
图12-3 AppWizard第1步
AppWizard的第1步用于指定应用程序的类型,可选择单文档界面(SDI)、多文档界面(MDI)或基于对话框的界面。如果要创建不需要文档对象从磁盘文件中读取数据的简单应用程序,可把标为Document/View Architecture Support(文档/视图结构支持)的复选项设置为禁用(见图12-3)。
对于一次只处理一个文档对象的SDI应用程序,可选择Single Document(单文档)按钮。SDI应用程序的规模比起相应的MDI应用程序来说要小得多,因此其可执行文件也更小一些。
MDI应用程序的优点是能一次处理多个文档。MDI应用程序在每个分离的窗口中显示一个文档,用户可以在不同的文档窗口中工作,并把每个文档单独保存为一个文件。其实,Developer Studio本身也是MDI应用程序,可在不同的编辑窗口中显示各种文本或非文本数据。
基于对话框的应用程序对于那些不需要一个主窗口的小实用程序非常适合,因为用户通过对话框就可与程序交流。基于对话框的应用程序将在第14单元中介绍。
AppWizard的步骤1还要求程序员选择程序界面上文字的语种。可选的语言取决于安装到计算机系统上的AppWizard库;单击文本框附近的箭头按钮,可显示可选的语言。每种语言都有对应的动态链接库,默认情况下,这些动态库都安装在Common\MsDev98\bin\ide文件夹中。库文件名以Appwzxxx.dll的形式表示,其中xxx表示一个三位的语言代码。例如,chn代表中文,enu代表美国英语,deu代表德语,而fra代表法语。
步骤2:数据库支持
图12-4 AppWizard第2步在AppWizard的第2步中,要求程序员说明项目所需要的数据库支持(图12-4)。这一步及其后的步骤假定在第一步中选择了SDI或MDI选项。
如果应用程序不使用数据库,单击Next按钮跳过这一步,直接进到步骤3。
该步骤的对话框左部的四个单选按钮决定了AppWizard添加到项目中的数据库支持程度:
None(无):在生成项目时,把数据库支持的库文件排除在外。如果项目不使用数据库,选择None单选按钮,可以避免在项目文件增加不必要的代码。当然,即使在创建项目时没有生成数据库支持代码,程序员仍可在开发过程中向项目中添加数据库支持。
Header files only(只有头文件):只生成数据库包含头文件和库文件,但不为数据库类生成源代码。程序员必须自己写所有的源代码。该选项适用于那些开始时不使用数据库,但打算以后添加数据库支持的项目。
Database view without file support(数据库视图,没有文件支持):包含数据库头文件和库文件,同时也创建一个记录视图和记录集。生成的应用程序支持文档,但不支持系列化操作。
Database view with file support(数据库视图,具有文件支持):设置同上,生成的应用程序框架既支持数据库文档,也支持序列化操作。
如果选用最后两个选项中的一个来生成数据库视图,那么,除非指出数据源,否则不能继续到下一步骤。
要说明的是,本教程并不涉及数据库编程方面的内容。因此本教程中的示例程序和练习程序的第2步均使用缺省设置(None),即不生成数据库支持。如果读者对数据库编程有兴趣,可参看有关参考文献。
步骤3:OLE和ActiveX支持
图12-5 AppWizard的第3步
AppWizard的第3步用于设置应用程序的OLE和ActiveX支持类型(图12-5)。对话框上半部分的五个单选按钮控制着AppWizard添加到程序中的复合文档支持的类型。下面是这些选项的说明:
None(无):AppWizard不为复合文档支持生成任何代码。
Container(包容器):AppWizard创建能包含链接和嵌入对象的程序。
Mini-server(微型服务器):应用程序充当一个微型服务器。微型服务器能创建复合文档对象,集成应用程序可把这些对象合并到自己的文档中。这样的文档对用户来说,就像单一文档一样,但实际上它是由不同的来源形成的。微型服务器直接把数据写入集成应用程序的文档中,而不是写在磁盘文件中。因此,对微型服务器创建的对象,集成应用程序可以嵌入,但不能链接。微型服务器应用程序不能作为独立的程序运行,而必须由集成应用程序来启动。Microsoft Draw就是微型服务器的一个例子。
Full-server(全服务器):AppWizard创建的应用程序可充当一个全服务器。全服务器除具有微型服务器的全部特性外,还有一些附加性能。像微型服务器一样,全服务器应用程序可由集成应用程序来启动,但全服务器也能作为一个独立的Windows应用程序来运行。AppWizard增加了对把数据存储到磁盘文件中的支持,因此,全服务器应用程序能像支持嵌入一样支持链接。
Both container and server(包容器和服务器):AppWizard生成代码以使应用程序既能像集成应用程序那样嵌入对象,又能像服务器应用程序那样提供对象。
图12-6 AppWizard的第4步第三步的对话框底部的两个复选项询问,是否要求Automation和ActiveX控件支持。默认情况下,AppWizard激活ActiveX Controls(ActiveX控件)选项;如果程序不会嵌入ActiveX控件,请清除该复选项。这个决定并不是不可取消的,以后,只要添加一行代码,就可增加对ActiveX控件的支持。
步骤4:用户界面特征
AppWizard的第4步中可以控制为应用程序创建哪些用户界面元素(图12-6)。
AppWizard自动为应用程序的主窗口菜单、工具栏及状态栏生成代码和数据。工具栏包含与菜单命令效果一样的按钮,而状态栏则显示命令和工具栏按钮的描述信息。当光标在菜单命令或工具栏按钮上停留一段时间后,帮助信息就会出现在状态栏上。没有选中命令时,状态栏显示诸如“Ready”、“For Help,press F1”或任何其他信息。状态栏也包括了键盘的Caps Lock、Num Lock和Scroll Lock键的指示器。当程序运行时,MFC框架自动更新指示器。
Normal(常规)和Internet Explorer Rebars单选按钮为应用程序的工具栏提供两种不同的风格。选择ReBars选项,会使用MFC的新的CReBar类来为工具栏生成代码,最终会生成象Developer Studio、Internet Explorer及其他应用程序一样的平面工具栏风格。平面工具栏是可以调整大小的,只有当鼠标移到按钮上面时,该按钮才凸起。菜单、工具栏和状态栏将在第13单元详细讨论。
默认情况下AppWizard激活Printing And Print Preview(打印和打印预览)复选项。该选项向应用程序的视图类中添加支持打印和打印预览的代码。关于打印编程,将在第13单元中讨论。
图12-7 高级选项对话框的字符串选项卡激活Context-Sensitive Help(上下文相关帮助)复选项,可通知AppWizard你希望应用程序提供在线帮助。AppWizard会向项目中添加源代码和一系列文件来帮助你创建一个完整的帮助系统。对于AppWizard添加到应用程序中的所有的命令和工具栏按钮,例如New、Open、Cut和Paste,AppWizard会自动生成相应的说明信息。这些说明清晰完整,无需再做更多的工作。程序员只需说明那些自己添加到程序中的命令来加强帮助文件。
在AppWizard第四步对话框的右下角,是一个Advanced(高级)按钮。单击该按钮将显示一个标题为“Advanced Options(高级选项)”的双页对话框。在第一页(图12-7)的Document Template Strings(文档模板字符串)中,可以改写储存在MFC框架使用的程序数据中的某些字符串,用于设置文档视结构的一些属性。这一部分包括以下几个编辑框:
编辑框 说明
File Extension 指定应用程序创建的文档所用的文件名后缀;
File ID 在Windows95的注册数据库中标识应用程序的文档类型;
MainFrame Caption 主框架窗口使用的标题;
Doc Type name 文档类型名;
Filter Name “打开文件”、“保存文件”对话框中的过滤器;
File new name(short name) 用于指定在new对话框中使用的文档名;
File Type name(long name) 当应用程序作为OLE Automation服务器时使用的文档类型名。
图12-8 高级选项对话框的窗口风格选项卡另一页是Window Styles(图12-8),用于设置主框架窗口的一些属性,包括框架窗口是否使用最大化按钮、最小化按钮,窗口启动时是否最大化或最小化等。针对项目是SDI还是MDI,可设置的属性也有变化。
按OK按钮,关闭Advanced Option对话框。
步骤5:使用MFC库
AppWizard的第五步用于设置应用程序的风格、是否需要附加的源代码注释,以及应用程序如何链接到MFC库。
图12-9 AppWizard的第5步
AppWizard提供了两种程序风格,由步骤5对话框顶部的两个单选按钮来决定。默认按钮为MFC Standard(MFC标准),对于创建带有从CView派生而来的视图类的普通Windows应用程序,应该选用这个选项。另一个按钮标题为Windows Explorer(Windows资源管理器),用于创建有着与大家熟知的Windows所带的Explorer(资源管理器)实用程序相似外观和用户界面的应用程序。
Explorer形式的应用程序的主窗口被分隔成相邻的两个窗格,每一个窗格显示不同的内容,而且每一个窗格都由它自己的类来控制。左窗格的视图类派生自MFC的CTreeView,它使该窗格适合于显示一个通过树形层次相关联的条目列表,例如公司的职员列表、家谱表或硬盘上的目录和文件列表。对应于右边窗格的视图类派生自CListView,被设计用来显示在某些方面属于左边窗格当前选中的选项的条目列表。像Explorer一样,程序的工具栏包含了四个调整窗格外观的附加按钮,它允许用户选择大图标或小图标的不同排列。
要求源文件注释会使AppWizard在生成的源代码中添加有用的注解。这些注解建议你添加相应的源代码,以使一个特征或函数变得可操作。例如:
void CDemoDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
// TODO:add storing code here
}
else
{
// TODO,add loading code here
}
}
选择添加源文件注释,也会使AppWizard在项目文件夹中放置一个ReadMe.txt文件。ReadMe文件作为整个项目的目录列表,提供了对AppWizard生成的所有文件的简要说明。
第五步中的第三个选项决定程序如何链接到MFC。默认设置为As A Shared DLL(作为共享的DLL),也可选用“As A Statically LinkedLibrary(作为静态链接库)”。动态链接和静态链接各自的含义可参看9,8:“用Visual C++集成开发环境开发Win32应用程序”。
实际上,在第五步中选择的链接方式选项仅仅是项目的初始设置,在程序开发过程中随时可通过Developer Studio的Project/Settings…菜单选项重新设置项目的链接方式。
步骤6:类和文件名
图12-10 AppWizard的第6步
AppWizard的第六步(也是最后一步,见图12-10)逐条列举AppWizard将为项目创建的类。在这一步,可以修改某个类的名字以及源代码的文件名。但包含应用程序类的源文件的名字取自项目,因此不能被改变。
在这一步,还可能修改某些派生类的基类,例如视图派生类就可选CEditView作为其基类,以实现文本编辑功能。
单击Finish(完成)按钮后,AppWizard弹出一个New Project Information对话框,显示新项目的各项设置。该摘要是取消项目的最后机会。单击OK即可在指定目录下创建项目。
12.10 Developer Studio的Workspace(工作区)窗口
Developer Studio在停靠的Workspace(工作区)窗口中显示项目的有关信息,如图12-11所示。
如果Workspace窗口被隐藏了起来,要使Workspace窗口可见,只要在View(查看)菜单中单击其名字即可。Workspace窗口也可通过Standard(标准)工具栏上的按钮来激活或隐藏。
图12-11 Workspace
Workspace窗口显示了应用项目各个方面的信息。在窗口底端选择相应的选项卡来显示项目的类、资源、数据源和文件的列表。在窗口中单击小的加号(+)或减号(()可展开或折叠列表。例如,展开类的列表会显示出成员函数的名字。
Workspace窗口的各选项卡如下:
ClassView:列出项目中的类和成员函数。要在Developer Studio的文本编辑器中打开类的源文件,可双击列表中的要打开的类或函数。
ResourceView:列出项目的资源数据,如对话框和位图。同ClassView中一样,双击ResourceView列表中的数据项会打开合适的编辑器并加载资源。
FileView:列出项目的源文件。注意,只有用菜单项Project/Add To Project命令才可明确地将新文件添加到项目中,仅仅将一新文件拷贝到项目的目录下是不够的。
Data View:显示数据库项目的数据来源信息。DataView(数据视图)选项卡只出现在Visual C++的企业版的数据库项目中,企业版遵循开放式数据库互连标准(ODBC)来同数据源相连。
在Workspace(工作空间)窗口中的某项上右击鼠标,会显示一个含有常用命令的上下文相关菜单。菜单中的命令取决于单击在哪一项上。例如,在FileView选项卡中的一个源文件上,右击鼠标会显示一个快速打开或编译文件的上下文相关菜单。
程序设计举例
[例12-2] 分析由AppWizard自动生成的应用程序框架。
说 明:用AppWizard生成一个SDI应用程序框架,各项设置均使用缺省值。
程序分析:AppWizard为应用程序项目生成了大量文件。下面以其中的视图类头文件和源程序文件为例,说明其一般结构。
用鼠标左键双击工作区窗口的ClassView页中的CMyView类,便可在文本编辑器窗口打开视图类的头文件。
头文件的的开始以注解形式给出文件名及其说明。接下来是一段预处理命令:
#if !defined(AFX_1202VIEW_H__B286EBCE_DAEC_12D3_869D_B24E5D7C4FD6__INCLUDED_)
#define AFX_1202VIEW_H__B286EBCE_DAEC_12D3_869D_B24E5D7C4FD6__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
在AppWizard生成的每个头文件的前面都有这么一段,形式离奇又没有注解,很难于理解。在AppWizard生成的程序中,类似这种情况的代码还有很多。其实,这些代码是为Developer Studio自身准备的,其类向导、资源编辑器和编译程序等都可能用到这些代码。我们在阅读程序时,完全可以跳过这些段落。
接下来是视图类的声明。
class CMyView,public CView
{
protected,// create from serialization only
CMy1202View();
DECLARE_DYNCREATE(CMy1202View)
// Attributes
public:
CMy1202Doc* GetDocument();
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CMy1202View)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC,CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC,CPrintInfo* pInfo);
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CMy1202View();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
protected:
// Generated message map functions
protected:
//{{AFX_MSG(CMy1202View)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
从这段代码中可以看到,AppWizard生成的源代码质量很高,不但考虑到了编程中的各种可能性,而且还加有简明扼要的注解。程序员所要做的工作只是在适当的位置添加一些与特定程序功能有关的数据成员或成员函数的定义。值得说明的是,许多数据成员和成员函数,特别是控件成员和消息映射函数,还可以通过Developer Studio的ClassWizard(类向导)自动加入程序。
当然,如果有需要,程序员也可以修改AppWizard生成的代码。但是,除非有特殊理由,不要随意修改程序中以灰色显示的代码,例如Overrids段。这些代码最好由ClassWizard维护。
注意,不要因为没有使用某个成员函数而删去其声明或定义代码。开发Visual C++程序的一般做法是,只添加必须的代码,忽略其他部分,尽量少作删除和修改。
展开工作区ClassView页上的MyView子目录,可以看到MyView类所有成员函数名。在任一函数名上双击鼠标左键,就可打开MyView类的源代码文件并将文本光标置于该函数开始处。
源程序文件比较简单,首先是一些文件包含命令,接下来的是消息相应宏。最后是视图类各成员函数的定义。一个典型的成员函数定义如下:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO,add draw code for native data here
}
其中包括一些几乎可以肯定需要的代码和如何添加代码的注解。程序员可以任意修改其内容以实现自己的意图,也可以添加新的成员函数。
单元上机练习题目用Developer Studio的AppWizard生成一个SDI框架程序,然后将其改造为与例12-1功能相同的吹泡泡程序。
在上题的基础上增加用鼠标右键删除泡泡的功能。
使用AppWizard重新编写一吹泡泡程序,用鼠标右键生成泡泡,鼠标左键移动泡泡的位置。