第12单元文档/视图结构 235 第12单元文档视图结构 本单元教学目标 以单文档界面(SDⅠ)应用程序为例,介绍MFC的文档视图程序结构 学习要求 理解文档/视图结构,可在 AppWizard生成的SD程序框架的基础上添加必要的代码 以生成自己的应用程序。 授课内容 大部分应用程序均要使用数据,其主要工作可以分为两部分:一是对数据的管理,如存 储、复制和查询等任务,一是对数据的处理和输入输出,包括显示和打印。MFC提供了“文 档/视图”结构支持这类应用程序 121文档/视图概念 在文档/视图结构里,文档可视为一个应用程序的数据元素的集合,MFC通过文档类提 供了大量管理和维护数据的手段。视图是数据的用户界面,可将文档的部分或全部内容在其 窗口中显示,或者通过打印机打印出来。视图还可提供用户与文档中数据的交互功能,将用 户的输入转化为对数据的操作。 在MFC中,有两种类型的文档视结构,即单文档界面(SDl: Single document Interface) 应用程序和多文档界面(MDl: Multiple Document Interface)应用程序。 在单文档界面(SD)程序中,用户在同一时刻只能操作一个文档,例如 Windows的 NotePad(记事本)程序采用的就是单文档界面。在SD程序中,打开新文档时会自动关闭 当前打开的活动文档,如果当前文档修改后尚未保存,会提示用户是否保存所做的修改。 SDI应用程序一般都提供一个File菜单,在该菜单下有一组命令,用于新建文档(New)、 打开己有文档(open)、保存或换名存盘文档等。这类程序相对比较简单,常见的应用程序 有终端仿真程序和一些工具程序。 多文档界面(MD1)应用程序允许同时对多个文档进行操作,例如 Microsoft Word和 Developer Studio本身采用的都是多文档界面。在M程序中可以打开多个文档(同时也为 每个文档打开一个窗口),可以通过切换活动窗口激活相应的文档进行编辑处理。MD1应用 程序也提供一个File菜单,用于新建、打开和保存文档。与SDl应用程序不同的是,MDI
第 12 单元 文档/视图结构 - 235 - 第 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
第12单元文档/视图结构 的File菜单中还有一个 Close(关闭)菜单选项,用于关闭当前打开的文档。MDI应用程序 还提供一个窗口菜单,管理所有打开的子窗口,包括对子窗口的新建、关闭、层叠、平铺等。 关闭一个窗口时,窗口内的文档也被自动关闭。在本单元中,我们只讨论SDI应用程序的 结构,MDI应用程序在第16单元中介绍。 文档/视图结构大大简化了多数应用程序的设计开发过程。其特点主要有: 1.将对数据的操作与数据显示界面分离,放在不同类的对象中处理。这种思想使得程 序模块的划分更加合理。文档对象只负责数据的管理,不涉及用户界面;视图对象只负责数 据输出和与用户的交互,可以不考虑数据的具体组织结构的细节 2.MFC在文档/视图结构中提供了许多标准的操作界面,包括新建文件、打开文件 保存文件、文档打印等,大大减轻了程序员的工作量。程序员不必再书写这些标准处理的代 码,从而可以把更多的精力放到完成应用程序特定功能的代码上。 3.支持打印、打印预览和电子邮件发送功能。程序员只需要编写很少的代码甚至根本 无需编写代码,就可以为应用程序提供“所见即所得”式的打印和打印预览这类功能。 4使用 Developer Studio的 App Wizard可生成基于文档/视结构的SD或MD框架程序, 程序员只需在其中添加与特定应用有关的部分代码,就可完成应用程序的开发工作。 然而,文档/视图结构也不是万能的。有两种情况不宜采用文档/视图结构: 1.不是面向数据的应用程序或数据量很少的应用程序,如 Windows自带的磁盘扫描程 序、时钟程序等工具软件,以及一些过程控制程序等 2.不使用标准窗口界面的程序,象一些游戏软件等 122文档视图结构程序实例 例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 1 IDR MAINFRAME MENU PRELOAD DISCARDABLE BEGIN
第 12 单元 文档/视图结构 - 236 - 的 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
第12单元文档/视图结构 237 OPUP"文件(&F) MENUITEM"新建(&N)\ tCtrl+N ID FILE NEW MENUITEM"打开(&0)..\ tCtrl+0", 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 STRINGTABLE PRELOAD DISCARDABLE BEGIN IDR MAINFRAME"吹泡泡\ n\nBub\nUb文件(*.bub)n.bub END lude"l. chs\\afxres.rc #include". chs\\afxprint rc 然后编译、连接项目 程序 / Example12-1:吹泡泡(最小文档视图框架)〃/ #include #include include /文档类 class CMy Doc public DOcumen DECLARE DYNCREATE( CMy Doc CArray m rect Bubble;/泡泡数组 int GetListSizeo freturn m rectBubble GetSizeo: 1 CRect Get Bubble(int index) freturn m rectBubble [index]: 1 void AddBubble( CRect rect)(m rectBubble Add (rect): H virtual BOOL On NewDocumento virtual void Delete Contents
第 12 单元 文档/视图结构 - 237 - 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 #include #include // 文档类 //////////////////////////////////////////////////// class CMyDoc : public CDocument { DECLARE_DYNCREATE(CMyDoc) CArray 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();
238 virtual void Serialize(CArchive& ar) IMPLEMENT DYNCREATE( CMy Doc, CDocument) /构造函数:对SDI仅调用一次,做初始化工作 CMy Doc: CMy O m rect Bubble. Setsize(256,256);//设置数组参数 /打开新文档:每次打开新文档时调用,做某些初始化工作 BOOL CMy Doc: OnNewDocument o if ( CDocument: OnNewDocument O) return False srand( unsigned)time(NUL);∥/初始化随机数发生器 return TRUE ∥/清理文档:关闭文档、建立新文档和打开文档前调用 void CMy Doc: DeleteContentso m rect Bubble. RemoveAll0;∥/泡泡数组清零 CDocument:: DeleteContents o /系列化:读写文档时自动调用 void CMyDoc: Serialize(CArchive &ar) m rectBubble Serialize(ar) /视图类/ class Cmy View: public Cview DECLARE DYNCREATE ( CMy View) ablie CMy Doca* GetDocumento freturn(CMy Doc*)m pDocument: 1 virtual void OnInitialUpdate o virtual BOOL On PreparePrinting(CPrintInfo* pInfo) virtual void OnDraw(CDC* pDC) afx msg void OnLBut tonDown UINT nFlags, CPoint point) DECLARE MESSAGE MAP O
第 12 单元 文档/视图结构 - 238 - 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()
第12单元文档/视图结构 239 IMPLEMENT DYNCREATE (CMyView, CView BEGIN MESSAGE MAP(CMyView, CView) ON WM LBUTTONDOWN O 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 MAPO /更新初始化:当建立新文档或打开文档时调用 void CMyView:: OnInitialUpdateO CView:: OnInitialUpdate o Invalidate o;//更新视图 ∥/绘制视图:程序开始运行或窗体发生变化时自动调用 void CMyView: OnDraw (CDC* pDC) CMy Doc* pDoc GetDocumento /取文档指针 ASSERT VALID(pDoc pC-> SelectStockOb ject( LTGRAY BRUSH);//在视图上显示文档数据 for (int i=0: iGetListSizeo: i++) pDC->Ellipse(pDoc->GetBubble(i)) ′消息响应:用户点击鼠标左键时调用 void CMy View:: OnLBut ton Down( UINT n Flags, CPoint point) CMy Doc* pDoc GetDocumento) /取文档指针 ASSERT VALID(pDoc) int r= rand (%50+5: /生成泡泡半径 CRect rect Bubble(point x-r, point. y-r, point x+r, point. ytr) pDoc->AddBubble(rectBubble /修改文档数据 pDoc->SetModifiedFlago //设置修改标志 InvalidateRect( rect Bubble, FALSE);//更新视图 /准备打印:设置打印参数 BOOL CMyView:: OnPreparePrinting(CPrintInfo* pInfo) pInfo> SetMaxPage(1);∥/设置打印页数
第 12 单元 文档/视图结构 - 239 - }; 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; iGetListSize(); 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 mainframe128主框架的资源代号 class CMy app public CWinApp virtual BOOL InitInstance o DECLARE MESSAGE MAP O 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: OnFilePrint Setup) END MESSAGE MAP O ′初始化程序实例:建立并登记文档模板 BOOL CMyApp: InitInstanceo leDocTemplate* pDocTemplate pDocTemplate= new CSingleDocTemplate(//登记文档模板 IDR MAINFRAME RUNTIME CLASS(CMyDoc) RUNTIME CLASS(CMainFrame) RUNTIME CLASS (CMy View)) CCommandLinelnfo cmdInfo //创建及处理命令行信息SDI ParseCommandLine(cmdInfo) if ( ProcessShellCommand(cmdInfo)) m pMainWnd-> ShowWindow(SW_SHoW);//初始化框架窗口 m pMainWnd->UpdateWindowO return TRUE
第 12 单元 文档/视图结构 - 240 - 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;
//全局应用程序对象 CMy App theapp 输入输出:用鼠标左键可在窗口生成一系列泡泡。程序的框架窗口附有一个文件菜单, 其中包括“新建”、“打开”、“保存”和 “打印”等文档操作选项,如图121所 分析:在本程序中一共用到4 个类,即 CMy App、 CMy Wnd、 CMy View 0设置g 退出C) 和 CMyDoc,它们分别是应用程序类 CWinApp、框架窗口类 FRame Wnd、视 图类CVew和文档类 CDocument的派生 类 文档派生类 CMy Doc用于管理吹泡 泡程序的数据,即所有泡泡的包含矩形 图12-1SD结构的吹泡泡程序 由于我们无法断定用户会生成多少泡 泡,所以声明了一个数组 m rect Bubble来存放所有泡泡的包含矩形。 m rect Bubble是 CMyDoc类的私有数据成员,在类外不可见:所以要为其定义一组成员函数作为该数据成员 与外界的接口。函数 Get ListSize()用来统计数组中泡泡的个数:函数 GetBubble()用 于从数组中取一个泡泡的包含矩形:函数 Add Bubble()用于将一个泡泡的包含矩形加入数 组。由于这些成员函数都比较简单,所以采用内联函数的形式 视图派生类的 OnLButton Down()函数用于响应用户的鼠标消息, Ondraw()函数用 于在视图上输出(当然也是在框架窗口的客户区输出)。这些原来都是框架窗口类的工作, 在文档/视图结构中转移到视图类来了 由于数据放到了文档类中,消息响应和输出工作放到了视图类中,因此框架窗口类就显 得非常简洁了。 应用程序类与以前不同的是增加了三个消息响应宏,用于处理文件菜单的选项。由于这 些工作都是标准化的,所以无需重载消息响应函数 在应用程序类的 InitInstance()函数中建立了一个文档模板 在以下几节中,将根据本例详细说明文档/视图结构的程序构造 123文档/视图结构中的应用程序类 应用程序类负责唯一的全局应用程序对象的创建、初始化、运行和退出清理过程,这个 过程我们已经很熟悉了 对于文档/视图结构,要在应用程序类的 InitInstance()函数中创建一个文档模板,来 管理文档/视图结构涉及的框架窗口、文档和视图 文档模板负责在运行时创建(动态创建)文档、视图和框架窗口。一个应用程序对象可
第 12 单元 文档/视图结构 - 241 - } // 全局应用程序对象 CMyApp theApp; 输入输出:用鼠标左键可在窗口生成一系列泡泡。程序的框架窗口附有一个文件菜单, 其中包括“新建”、“打开”、“保存”和 “打印”等文档操作选项,如图 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()函数中创建一个文档模板,来 管理文档/视图结构涉及的框架窗口、文档和视图。 文档模板负责在运行时创建(动态创建)文档、视图和框架窗口。一个应用程序对象可 图 12-1 SDI 结构的吹泡泡程序
第12单元文档/视图结构 以管理一个或多个文档模板,每个文档模板用于动态创建和管理一个或多个同类型的文档 (这取决于应用程序是sD程序还是MD程序)。MFC的文档模板类 CDocTemplate用于支 持文档模板操作。由于文档模板类是一个抽象基类,因此不能直接用其声明对象,只能使用 其派生类。对于单文档界面程序,应使用 CSingleDoc Template(单文档模板类),对于一个 多文档界面程序,使用 CMultiple Doc Template(多文档模板类)。例12-1的吹泡泡程序是单 文档界面程序,所以使用单文档界面模板: CSingleDoc Template* pDoc Template pDoc Template new CSingle DocTemplate( IDR MAINFRAME RUNTIME CLASS(CMY D RUNTIME CLASS(CMain Frame) RUNTIME CLASS(CMy View)); 文档模板定义了文档、视图和框架窗口这三个类的关系。通过文档模板,可以知道在创 建或打开一个文档时,需要用什么样的视图和框架窗口显示它。这是因为文档模板保存了文 档及其对应视图和框架窗口的 CRuntimeclass对象的指针。此外,文档模板还保存了所支持 的全部文档类的信息,包括这些文档的文件扩展名信息、文档在框架窗口中的名字、代表文 档的图标等信息 由于文档模板要在运行时动态创建相应的框架程序对象、文档对象和视图对象,因此要 求程序中使用的上述类支持动态创建功能。通过 CMy Wnd、 CMy View和 CMy Doc类中的 DECLARE DYNCREATE()宏和程序中的 IMPLEMENT DYNCREATE()将它们说明为 动态创建类,文档模板即可动态创建这些类的对象。 在 SIngle Doc Template类的构造函数中,还有一个 IDR MAINFRAME参数,用于标明 资源文件中的某些资源。本程序共使用了两项资源,一个文件菜单和一个字符串(应用程序 名)。 在创建了文档模板之后, InitInstance()函数调用 Add Doc Template()函数将创建好 的文档模板加入到应用程序的可用文档模板链表中去。这样,如果用户选择了 File/New或 File/Open菜单选项要求创建或打开一个文档时,应用程序类的 OnNewDocument()成员函 数和 OnOpenDocument()成员函数就可以从文档模板链表中检索出相应的文档模板提示用 户选择适当的文档类型并创建文档及其相关的视图和框架窗口 应用程序类的消息映射 BEGIN MESSAGE MAP(CMy App, CWinApp) ON COMMAND(ID FILE NEW, CWinApp: OnFileNew) ON COMMAND(ID FILE OPEN, CWinApp: OnFileOpen) END MESSAGE MAPO 用于响应新立文档和打开文档消息,这些消息在用户选择文件处理菜单的有关选项时送出 相应的的消息处理函数 OnNewDocument()和 OnOpen Document()中已经包含了有关新 建文档和打开文档的全部标准代码,因此不必再重载这些函数并添加代码了
第 12 单元 文档/视图结构 - 242 - 以管理一个或多个文档模板,每个文档模板用于动态创建和管理一个或多个同类型的文档 (这取决于应用程序是 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()中已经包含了有关新 建文档和打开文档的全部标准代码,因此不必再重载这些函数并添加代码了
第12单元文档/视图结构 243 在创建了文档模板并将其加入应用程序的可用文档模板链表之后,还要创建及处理命令 行信息: CCommandLinelnfo cmdInfo Parse Command Line(cmdInfo) if(! ProcessShellCommand(cmdInfo)) return False 如果用命令行方式运行应用程序,则可使用命令行参数,通常为文档文件名。这时应用程序 会自动打开该文档;如果不使用命令行参数,则应用程序自动建立一个新文档。 在应用程序类的 InitInstance()函数的最后初始化并显示框架窗口。 124框架窗口类 与基于框架窗口的应用程序相比,文档/视图结构程序的框架窗口类显得十分简单。在 文档/视图结构中,视图在框架窗口中显示,是框架窗口的子窗口并完全覆盖框架窗口的客 户区。框架窗口的作用有二:一是为视图提供可视的边框,包括标题条、一些标准的窗口组 件(最大、最小化按钮、关闭按钮),象一个容器一样把视图包装起来。二是响应标准的窗 口消息,包括最大化、最小化、调整尺寸等。当框架窗口关闭时,其中的视图类对象也被自 动删除 为简洁起见,例12-1的吹泡泡程序的框架窗口派生类省略了几乎所有可选内容,包括 创建状态栏和工具栏的工作。 125视图类 视图类Cvew是窗口类CWnd类的派生类。视图类对象完全覆盖框架窗口的用户区, 没有自己的边框。视图规定了用户查看文档数据以及同数据交互的方式 视图类有几个重要的成员函数 GetDocument()成员函数用于从文档类中获取数据值。实际上,该函数提供一个指向 文档派生类对象的指针,通过该指针可访问文档派生类的公有成员。例如 Get Document()->Add Bubble( rect Bubble ) 正因为如此,文档类的数据成员只能声明为公有的,而不能象面向对象技术所要求的那样, 将所有的数据成员均声明为私有成员。由于文档类和视图类的关系十分密切,这样做可以简 化程序设计,并不会因封装性被破坏而造成混乱 在视图类中,用 OnDraw()成员函数更新视图,其用法类似窗口类的 OnPaint()函 数。所不同的是, OnDraw()函数有一个指向CDC类的指针参数,通过该参数可以直接向 视图输出。 应用程序框架调用视图的 CView: OnDraw(CDC*pDC)方法完成屏幕显示、打印和打 印预览功能,对于不同的输出功能会传递不同的DC指针给 OnDraw()函数
第 12 单元 文档/视图结构 - 243 - 在创建了文档模板并将其加入应用程序的可用文档模板链表之后,还要创建及处理命令 行信息: 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()函数
第12单元文档/视图结构 244 在 OnDraw()函数中,首先调用 Get document()函数,取得指向当前视图所对应的 文档的指针。然后通过这个指针来访问文档中的数据。 在绘图时,可以通过传给 On Draw()函数的设备上下文指针pDC进行GD调用。开 始绘图之前,往往需要选择GDI资源(或GDI对象,包括画笔、刷子、字体等),将其选 入设备环境。绘图代码是与设备无关的,也就是说在编写这些代码时并不需要知道目前使用 的是什么设备(屏幕、打印机或其他绘图设备)。 视图类的 OnInitialUpdate()虚成员函数在应用程序启动,或用户从Fle菜单中选择了 New或者open选项时被调用。因此,这是添加某些与文档显示有关的初始化工作的有关代 码的好地方。重载该虚函数时要注意确保调用了基类CⅤiew的 OnInitialUpdate()成员函数 126文档类 文档类 CDocument的派生类对象规定了应用程序的数据 当用户启动应用程序,或从应用程序的File菜单中选择New选项时,都需要对文档类 的数据成员进行初始化。我们知道,一般类的数据成员的初始化工作都是在构造函数中完成 的,在构造函数调用结束时对象才真正存在。但对于文档类来说却不同,文档类的数据成员 初始化工作是在 OnNewDocument()成员函数中完成的,此时文档对象已经存在。为什么 呢?这是因为,在单文档界面(SD)应用程序中,应用程序启动时,文档对象就己经被创 建,直到主框架窗口被关闭时才被销毁。在用户选择Fie/New菜单选项时,应用程序对象 并不是销毁原来的文档对象然后重建新的文档对象,而只是重新初始化(Re- Initialization) 文档对象的数据成员,这个初始化工作就是由应用程序对象的 OnfileNew()消息处理成员 函数通过调用 OnNew Document()成员函数来完成的。试想,如果把初始化数据成员的工 作放在构造函数中,由于对象已经存在,构造函数无法被调用,也就无法完成初始化数据成 员的工作。为了避免重复代码,在应用程序启动时,应用程序对象也是通过调用 OnNewDocument()成员函数来初始化文档对象的数据成员 在关闭应用程序并删除文档对象时,或用 File/Open菜单选项打开一个文档时,需要清 理文档中的数据。同文档的初始化一样,文档的清理也不是在文档的析构函数中完成,而是 在文档类的 DeleteContents()成员函数中完成的。文档类的析构函数只用于清除那些在对 象生存期都将存在的数据项(如使用new运算符生成的数据)。 DeleteContents()成员函数 的调用有两个作用: 1.删除文档的数据 2.确信一个文档在使用前为空。 注意, OnNewDocument()函数也会调用 DeleteContents()函数。在用户选择 File/Open 菜单选项时,应用程序对象调用应用程序类的 On FileOpen()成员函数,提示用户输入文 件名,然后调用 CWinApp: OpenDocumentFile()函数打开一个文件。 Open Document File() 函数在打开文件后首先调用 DeleteContents()成员函数清理文档中的数据,确保消除以前 打开的文档的数据被清理掉。 缺省的 DeleteContents()函数什么也不做。在编写应用程序时,需要重载 DeleteContents
第 12 单元 文档/视图结构 - 244 - 在 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