windows编程 12文档与视图

更新时间:2024-05-09 09:06:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第12章 文档与视图

MFC提供了将应用程序的数据与显示分开的文档-视图结构,它为应用程序提供了统一的框架,参见图12-1和图12-2。MFC的文档-视图结构是MVC模式的一种部分实现。

应用程序对象 指向:● 创建 文档模板 指向:● 创建 创建 文档对象 指向:● 框架窗口 指向:● 创建 视图对象 指向:● 图12-1 SDI应用程序中的对象 图12-2 文档-视图结构中各种对象及其创建与关系

12.1 MVC

MVC是一种软件架构模式,通过分解程序的不同功能,达到降低程序设计的复杂度、利于程序员专业分工、简化程序的维护和扩展的目的。MVC是1979年挪威计算机科学家Trygve Reenskaug在Xerox(施乐公司)PARC(Palo Alto Research Center,帕洛阿尔托研究中心)工作时,为(历史上第二个面向对象程序设计语言和第一个真正的集成开发环境)SmallTalk提出的。

12.1.1 概念

MVC(Model-View-Controller,模型—视图—控制器)是一种软件架构模式,它把软件系统分为如下三个基本部分(参见图12-3):

? 模型(Model)—— 数据(库)。 ? 视图(View)—— 图形界面(表示)。

1

? 控制器(Controller)—— 程序功能(算法)。

图12-3 MVC模式的关系图

MVC模式的目的是实现一种动态的程序设计,简化对程序的后续修改和扩展,并且使程序某一部分的重复利用成为可能。除此之外,此模式通过对复杂度的简化,使程序结构更加直观。

软件系统通过对自身基本部份分离的同时,也赋予了各个基本部分应有的功能,专业人员可以通过自身的专长进行分组合作:数据库专家利用“模型”进行数据管理和数据库设计、界面设计人员利用“视图”进行图形界面设计、而程序员则利用“控制器”编写程序应有的功能(实现算法等等)。

12.1.2 层次

模型(Model,数据模型)用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权利,例如对数据库的访问。“模型”不依赖“视图”和“控制器”,也就是说,模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制,那些用于监视此模型的视图必须事先在此模型上注册,从而,视图可以了解在数据模型上发生的改变。(比较:观察者模式(软件设计模式))

视图(View) 视图层能够实现数据有目的的显示(理论上,这不是必需的)。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能,视图需要访问它监视的数据模型(Model),因此应该事先在被它监视的数据那里注册。

控制器(Controller) 控制器起到不同层面间的组织作用,用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。

2

12.1.3 实现

? SmallTalk

1980年MVC最早被应用于Xerox PARC的面向对象、动态类型、自反射的编程语言SmallTalk-80环境中,运行在首个具有GUI的原型计算机Alto(男高音)上。

? MacApp

1985年Apple将MVC用于其推出的Mac OS系统的面向对象的应用程序框架MacApp中,这是MVC首次在商用产品中的实际应用。

? MFC

1993年2月微软在其随Visual C++ 1.0推出的MFC 2.0中,引入了文档-视图(Document / View)架构,它也是一种对于MVC的早期部分实现。MFC将程序分成视图(View)和文档(Document)两大类,其中的CDocument对应MVC中的数据模型(Model),CView相当于MVC中的视图+控制(View+Controller),再加上应用程序类CWinApp,合成三大项。

但是MFC基本上是一个失败的MVC作品。由于MFC对Document/View的定义过于模糊,未将Controller(消息映射)部份取出,因此Controller既可置入View也可置入Document,但不管置入哪一方面,都会与View或Document绑死而缺乏弹性。

? Java EE——Struts

1999年12月Sun推出的Java EE(Java Enterprise Edition,Java企业版,原来叫J2EE)和其他的各种框架不一样,它为模型对象(Model Objects)定义了一个规范。典型例子是由Craig McClanahan于2000年5月所开发的开源Java EE轻型Web应用框架Apache Struts。

? 视图(View) —— 在JAVA EE应用程序中,视图(View)可能由JSP(Java Server

Page,爪哇服务器网页)承担。生成视图的代码则可能是Servlet的一部分,特别是在客户端服务端交互的时候。

? 控制器(Controller) —— JAVA EE应用中,控制器可能是一个Servlet。 ? 模型(Model) —— 模型则是由一个实体Bean来实现。 ? .NET——Windows Forms

2002年2月微软所推出的.NET框架中还包含有WinForms(视窗窗体),这个针对视图(View)和控制器(Controller)的模式已被很好地定义,而模型(Model)则留给开发者去设计。

? 视图(View)—— 由Form或者Control类继承来的一个类处理视图的职责。

3

在WinForm这个例子中视图和控制器被编译在同一个类中,这个和ASP.NET不同。

? 控制器(Controller)—— 控制器的职责被分割成三部分。事件(Event)的产

生和传输是操作系统的一部分。在.Net框架中Form和Control类将不同的事件转发给相应的事件处理器。而事件的处理则在分离的代码中实现。 ? 模型(Model)—— 就像ASP.NET一样,WinForm不严格需要一个模型。开

发者可以自行选择创建一个模型类,但是很多人选择放弃这一步,直接把事件处理放在控制器里处理任何计算、数据保存等等。也就是说用模型来包含商业逻辑和数据访问。

? .NET——ASP.NET

2002年2月微软随.NET推出了ASP.NET,针对视图(View)和控制器(Controller)的模式并没有被很好地定义,模型(Model)也留给开发者去设计。

? 视图(View)——ASPX和ASCX文件被用来处理视图的职责。在这个设计中

视图实际上是从控制器继承而来。这个和Smalltalk的实施有所不同,在Smalltalk中不同的类都有指针互相指向对方。

? 控制器(Controller)—— 控制器的职责被分割成两部分。事件(Event)的产生

和传输是框架的一部分,更明确的说是Page和Control两个类。而事件的处理则在分离的代码中实现。

? 模型(Model)—— ASP.NET 不严格需要一个模型。开发者可以自行选择创

建一个模型类,但是很多人选择放弃这一步,直接把事件处理放在控制器里处理任何计算、数据保存等等。但用模型来包含商业逻辑和数据访问是可实现的。

2009年4月9日微软推出了ASP.NET MVC 1.0,它在ASP.NET 3.5运行库之上提供了一个新的MVC架构。此架构为Web应用程序文件夹(folder)结构定义了一个特别模式,并提供了一个控制器基类来处理“动作(actions)”请求。ASP.NET MVC 2.0将随.NET框架4.0和Visual Studio 2010一起推出。

12.2 文档-视图体系

文档(document)对应于用户的数据(可以是文本、数值、图像、声音、视频等),它可以从磁盘文件中读入,也可写入磁盘文件,用户还可以创建、修改和管理这些数据。文档

4

对应的MFC类为CDocument。

视图(view)是一种窗口对象,对应于框架窗口的客户区,它负责在屏幕和打印机上显示和输出数据,为用户提供观察、选择、编辑文档数据的交互界面(参见图12-4)。视图对应的MFC类为CView。

文档-视图结构有两种主要的方式:SDI(Single Document Interface,单文档界面)和MDI(Multiple Document Interface,

图12-4 文档与视图

多文档界面)。从MFC 7.0起新增加了一种MTDI(Multiple Top-level Document Interface,多顶级文档界面),参见图11-5。

SDI

传统MDI

选项卡式MDI

MTDI

图11-5 不同的文档界面

SDI应用程序只有一个框架窗口(类)和一个视图窗口(对应于框架窗口的客户区),且只有一个文档类,每次只能打开一个文档。这里的文档和视图一般是一一对应的。例如Windows中的记事本、写字板和画图等软件,是典型的SDI应用程序。

MDI应用程序有一个主框架窗口(类),可有任意多个子框架窗口和对应的视图客户区窗口,也可有多个文档类,可以同时打开多个文档/窗口。这里,每个视图对应于一个文档,而每个文档则可对应于多个视图,参见图12-6。例如Word 2000和IE 8,分别是传统和选卡式MDI应用程序。

MTDI类似于MDI,只是MDI中的每个文档视图窗口都是主框架窗口的子窗口(只能位于主框架窗口的客户区内)。而MTDI的文档视图窗口都是顶层窗口,位于主框架窗口之外。例如新版Word和老版IE,就是典型的MTDI应用程序。

应用程序的文档-视图结构种类,可以在创建MFC应用程序时,在“MFC应用程序向导”对话框的“应用程序类型”页中设置(默认为MDI),在该对话框页中还可以选择是否

5

图12-6 一个文档可对应多个视图

具有“文档/视图结构支持”(默认是选中的),参见图12-7。

图12-7 MFC应用程序向导中的“文档/视图结构支持”选项

12.2.1 文档模板类

文档、框架窗口与视图通过文档模板联系在一起,MFC的文档模板类为CDocTemplate。对SDI与MDI,它有两个对应的派生类CSingleDocTemplate与CMultiDocTemplate,在MFC功能包中又增加了多文档模板的扩展类CMultiDocTemplateEx,参见图12-8。

图12-8 文档模板类的层次结构

它们的构造函数的参数都一样:

C[Single|Multi]DocTemplate[Ex] ( // 文档模板构造函数 );

6

UINT nIDResource, // 文档类型的资源ID // 派生文档类对象的指针

CRuntimeClass* pDocClass,

CRuntimeClass* pFrameClass, // [派生]框架窗口类对象的指针 CRuntimeClass* pViewClass

// 派生视图类对象的指针

CWinApp类创建文档模板的操作分两步进行,首先用文档模板类的构造函数创建一个SDI或MDI文档模板的实例,然后调用CWinApp类的成员函数AddDocument将该模板添加到应用程序的模板列表中。创建文档模板的操作,一般在派生应用程序类的InitInstance成员函数中完成。

例如 (SDI) :

BOOL CDrawApp::InitInstance() { }

其中,RUNTIME_CLASS宏返回一个指向CRunTimeClass类的指针:

CRuntimeClass* RUNTIME_CLASS( class_name )

又例如 (MDI) :

BOOL CImageApp::InitInstance() {

……

// 注册应用程序的文档模板。文档模板 // 将用作文档、框架窗口和视图之间的连接 CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CDrawDoc),

RUNTIME_CLASS(CMainFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CDrawView));

if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate); ……

……

// 注册应用程序的文档模板。文档模板 // 将用作文档、框架窗口和视图之间的连接 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(

IDR_BMPTYPE,

RUNTIME_CLASS(CImageDoc),

7

}

RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CImageView));

if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);

pDocTemplate = new CMultiDocTemplate(

IDR_GIFTYPE,

RUNTIME_CLASS(CImageDoc),

RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CImageView));

if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate); ……

可见一个MDI应用程序可有多个MDI模板,每个MDI模板在运行时又可有多个实例,对应于同一模板中同一文档类型/视图类型的多个文档对象/视图窗口,参见图12-9。

CMyApp 应用程序对象 CMultiDocTemplate 文档模板A 文档模板B 打开文档 CMultiDocTemplate 文档1 CMyDocA 文档2 CMyDocA 文档3 CMyDocA 文档1 CMyDocB 一个类的实例 另一个类的实例

图12-9 具有两个文档类型的MDI应用程序

12.2.2 文档类

所有用户的文档类都是从文档基类CDocument派生的,参见图12-10。

8

图12-10 文档类的层次结构

CDocument类的常用成员函数有(其中粗体表示最常用的):

? GetFirstViewPosition——获得视图列表中与本文档关联的第一个视图的位置,该位

置可用于GetNextView函数,原型为:

virtual POSITION GetFirstViewPosition( ) const;

? GetNextView——返回rPosition所指的视图的指针,获得下一个本文档关联的视图

的位置到rPosition中,原型为:

virtual CView* GetNextView( POSITION& rPosition ) const; 使用GetFirstViewPosition与GetNextView可遍历文档的所有视图。 ? GetTitle——返回文档(窗口)的标题,一般为相关联的文件名,原型为:

const CString& GetTitle( ) const;

? SetTitle——设置文档(窗口)的标题,原型为:

virtual void SetTitle( LPCTSTR lpszTitle );

? GetPathName——返回与文档相关联的文件路径串,无关联文件时返回NULL,原

型为:const CString& GetPathName( ) const;

? SetPathName——设置存取文档的默认路径(与文档窗口的标题),若bAddToMRU

= TRUE,则将该路径添加到最近使用 (most recently used,MRU) 文件的列表,原型为:virtual void SetPathName( LPCTSTR lpszPathName,

BOOL bAddToMRU = TRUE );

? IsModified——判断文档在最后一次存储后是否被修改过。若被修改过,则在用户

关闭文档窗口或应用程序时,会提示保存文件,原型为:BOOL IsModified( ); ? SetModifiedFlag——设置文档在最后一次存储后是否被修改过,原型为:

void SetModifiedFlag( BOOL bModified = TRUE );

? UpdateAllViews——在用户通过视图pSender修改了文档数据后,应调用该函数通

9

知所有与文档相关联的其他视图窗口。若pSender = NULL,则通知与文档相关联的所有视图窗口,该函数会调用每个视图类的OnUpdate成员函数,一般是在调用SetModifiedFlag后调用该函数。原型为:

void UpdateAllViews( CView* pSender, LPARAM lHint = 0L,

CObject* pHint = NULL );

? Serialize——默认时,派生的文档类会覆盖根类CObject的序列化成员函数Serialize

以支持文档的读写,原型为:

virtual void Serialize( CArchive& ar );

例如:

void CTestDoc::Serialize(CArchive& ar) {

if (ar.IsStoring()) { // 写入(对应于选中“文件”菜单中的“保存”项)

// TODO: add storing code here ar << m_nWidth << m_nHeight;

} else { // 读取(对应于选中“文件”菜单中的“打开”项)

// TODO: add loading code here ar >> m_nWidth >> m_nHeight;

}

? LoadStdProfileSettings——在派生应用程序类C*App的InitInstance成员函数中,默

认会调用该成员函数来支持MRU文件列表功能,原型为:

void LoadStdProfileSettings( UINT nMaxMRU = _AFX_MRU_COUNT ); 其中,_AFX_MRU_COUNT = 4,nMaxMRU可取的最大值为_AFX_MRU_MAX_ COUNT = 16,若nMaxMRU =0,则不支持MRU。例如:

LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU) LoadStdProfileSettings(10); // 自己设置 }

也可调用应用程序类的成员函数

virtual void AddToRecentFileList( LPCTSTR lpszPathName );

来向MRU文件列表中添加指定的文件路径串。MFC将MRU功能封装在从CObject类派生的CRecentFileList类中。

10

12.2.3 框架窗口类

因为视图窗口是框架窗口的子窗口,位于框架窗口的客户区。视图窗口本身只是一个没有边框、没有标题条、更没有菜单条和控制条的矩形区域。视图窗口不能单独存在,必须依附于一个框架窗口,参见图12-11 a)。主框架窗口负责管理标题条、菜单条、控制条、加速键和视图窗口或文档子窗口,参见图12-11 b)。

a) 框架窗口与视图

b) MDI框架窗口与子窗口

图12-11 框架窗口

框架窗口(类)也是通过文档模板(类)与视图(类)及文档(类)联系在一起的。MFC中框架窗口类为从窗口类CWnd派生的类CFrameWnd,参见图12-12。

SDI应用程序的主框架窗口一般直接以CFrameWnd为基类,例如:

class CMainFrame : public CFrameWnd {

protected: // 仅从序列化创建 }

但MDI应用程序的主框架窗口则一般以其派生类CMDIFrameWnd为基类,例如:

图12-12 框架窗口类的层次结构

CMainFrame();

DECLARE_DYNCREATE(CMainFrame) ... ...

class CMainFrame : public CMDIFrameWnd {

DECLARE_DYNAMIC(CMainFrame)

public:

11

}

CMainFrame(); ... ...

而MDI中文档子窗口的框架窗口,则从另一个框架窗口类CMDIChildWnd派生。例如:

class CChildFrame : public CMDIChildWnd {

DECLARE_DYNCREATE(CChildFrame)

public: }

CChildFrame(); ... ...

12.2.4 视图类

视图是文档与用户的接口,为用户提供观察、选择、编辑文档数据的交互界面。MFC中的视图类为从CWnd派生的CView类及其派生类。

MFC应用程序的视图类的基类一般为CView,其他常用的视图基类有CScrollView、C[Rich]EditView、CFormView、CHtmlView、(MFC 8.0新加的)CWndFormsView和(MFC 9.0功能包新加的)CTabView,参见图12-13。

1.CView类

CView是用户视图类的默认基类,也是其他各种MFC视图类的基类。例如:

class CTestView : public CView { protected: // 仅从序列化创建

CTestView();

DECLARE_DYNCREATE(CTestView)

// 属性 public: ……

CTestDoc* GetDocument() const;

图12-13 视图类的层次结构

12

// 重写 public: ……

protected:

CDocument* m_pDocument;

virtual void OnDraw(CDC* pDC); // 重写以绘制该视图

// 生成的消息映射函数 protected: };

1)CView类的常用成员函数有:

CDocument* GetDocument( ) const; // 获得对应文档类对象的指针 virtual void OnInitialUpdate( ); // 初始化虚消息响应函数,可覆盖 virtual void OnActivateView( BOOL bActivate, CView* pActivateView,

CView* pDeactiveView ); // 当视图窗口被激活或非激活时被调用

virtual void OnDraw( CDC* pDC ) = 0; // 纯虚函数,必须覆盖 2)视图类从其基类CWnd继承的常用成员函数有:

CDC* GetDC( ); // 获得视图窗口的DC

int ReleaseDC( CDC* pDC ); // 释放视图窗口的DC

void GetClientRect( LPRECT lpRect ) const; // 获得客户区矩形 void GetWindowRect( LPRECT lpRect ) const; // 获得窗口矩形 // 获得需重绘的最小矩形

BOOL GetUpdateRect( LPRECT lpRect, BOOL bErase = FALSE ); // 改变窗口的位置和尺寸

void MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE ); void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE ); CFrameWnd* GetParentFrame( ) const; // 获得父框架窗口的指针 CFrameWnd* GetTopLevelFrame( ) const; // 获得顶层框架窗口的指针 void Invalidate( BOOL bErase = TRUE ); // 使整个客户区矩形无效重画 void UpdateWindow( ); // 通过发送WM_PAINT消息来更新客户区

13

DECLARE_MESSAGE_MAP()

// 重绘窗口,相当于调用Invalidate和UpdateWindow

BOOL RedrawWindow(LPCRECT lpRectUpdate = NULL, CRgn* prgnUpdate = NULL,

UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);

CWnd* SetCapture( ); // 设置鼠标捕获

:: BOOL ReleaseCapture(VOID); // 释放设置鼠标捕获

CWnd* GetDlgItem( int nID ) const; // 获得nID对应的控件窗口指针

UINT GetDlgItemInt( int nID, BOOL* lpTrans = NULL, BOOL bSigned = TRUE ) const; // 获得/设置控件中显示的整数

void SetDlgItemInt( int nID, UINT nValue, BOOL bSigned = TRUE ); int GetDlgItemText( int nID, LPTSTR lpStr, int nMaxCount ) const; int GetDlgItemText( int nID, CString& rString ) const; // 获得/设置控件中显示的串

void SetDlgItemText( int nID, LPCTSTR lpszString );

2.GetDocument

默认情况下,MFC会自动在派生的视图类中覆盖其成员函数GetDocument,使其能够获得派生的文档类指针。应用程序中的GetDocument函数一般有两个版本:

? 在头文件中定义的正式版(内联函数):

#ifndef _DEBUG // TestView.cpp 中的非调试版本 inline CTestDoc* CTestView::GetDocument() const

{ return reinterpret_cast(m_pDocument); } #endif

? 在代码文件定义的测试版:

#ifdef _DEBUG ... ...

CTestDoc* CTestView::GetDocument() const // 调试版本是内联的 {

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTestDoc))); return (CTestDoc*)m_pDocument;

14

}

#endif //_DEBUG

在视图类中可以调用GetDocument函数来获得对应的文档对象,从而可显示、输出、编辑和修改文档数据。例如在OnDraw函数中MFC会自动加入的语句:

// CTestView 绘制

void CTestView::OnDraw(CDC* /*pDC*/) { }

CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc)

return;

// TODO: 在此处为本机数据添加绘制代码

12.2.5 创建过程

MFC应用程序的文档-视图结构中各个对象的创建,完全由系统控制,对用户是透明(黑箱)的。但是,为了能正确使用窗口和文档对象,我们需要了解它们创建的顺序和过程,参见图12-14。

用户打开文档 创建应用程序对象 用户新建文档 调用文档类的Serialize函数 创建文档对象 文档数据可用 调用视图类的OnInitialUpdate函数 创建主框架窗口对象 调用视图类的OnDraw函数 创建视图对象 调用视图类的各种消息响应函数 调用视图类的OnSize函数 图12-14 创建SDI应用程序的主要过程

15

1.创建框架窗口的过程

框架窗口的创建过程如图12-15所示。

文档模板:OpenDocumentFile

构造文档对象 CMyDoc 构造框架窗口对象 CMainFrame 框架 否 打开? 创建文档框架 是 调用CFrameWnd::Create 创建框架窗口 处理WM_CREATE消息 CMainFrame::OnCreate调用 CFrameWnd::OnCreateClient 创建客户区 打开文件并创建存档 调用CMyDoc:: OnNewDocument 调用CMyDoc:: OnOpenDocument 调用CMyDoc::Serialize 读取文档文件 关闭存档和文件 文档就绪可用 文档 CMyView::OnCreate 处理WM_CREATE消息 创建视图窗口 构造视图对象 CMyView 图12-15 框架窗口的创建过程

应用程序 ID_FILE_OPEN命令

调用处理函数 CWinApp::OnFileOpen 从用户获取文件名 使用文件扩展名来 选择文档模板 ID_FILE_NEW命令 调用处理函数 CWinApp::OnFileNew 一个文档 模板? 否 从用户获取文档类型 是 ●

文档模板在此点被选择 (MDI或SDI)

图12-16 创建和打开文档的过程

16

2.创建和打开文档的过程

MFC应用程序封装了创建和打开文档的过程,可用默认“文件”菜单中的“新建”和“打开”菜单项启动。MFC应用程序创建和打开文档的过程如图12-16所示。

3.初始化视图的过程

若希望在文档数据被显示之前,根据文档中的参数做一些初始化工作(如按图像的大小改变窗口的尺寸),可以在视图类中添加(重写型)消息响应函数OnInitialUpdate,并在此函数中作一些必要的文档操作。该函数是在窗口已经创建但还没有显示时会被系统调用,这时文档对象已经存在,而且文档文件已经被读取。需要说明的是,OnInitialUpdate函数不仅在程序启动(窗口创建)时会被调用,而且在每次打开或创建新文件时,也会被被系统调用。

视图初始化的过程如图12-17所示。

视图 WM_INITIALUPDATE 消息发送到视图 CMyView::OnInitialUpdate 处理消息 ●

视图在此点被初始化

默认OnInitialUpdate 调用CView::OnUpdate 图12-17 视图初始化的过程

4.创建应用程序过程

需要注意的是,应用程序中的文档对象虽然在视图对象创建之前已经被创建,但是文档类中(Serialize函数)对文件的读取则是在视图对象创建之后,而且是在视图类的OnCreate和OnSize函数被调用之后,但是是在视图类的OnInitialUpdate函数之前(参见图12-14和图12-18)。所以不能在视图类的构造函数中或OnSize的首次被调用时,使用GetDocument来获得用户文档的指针,不然得到的将是一个空文档的指针,利用该指针对文档类数据的任何操作都会导致系统的致命错误(因为其变量还没有初始化)。

另外,在系统第一次调用(窗口消息WM_SIZE的响应函数,需要自己添加)OnSize之前,文档还没有就绪,窗口也未初始化。所以,不能在首次调用OnSize函数时,进行窗

17

口操作或使用文档数据。解决办法是,在视图类中定义一个bool型类变量如m_bInit,在构造函数中将其初始化为false,在(重写型函数,需要自己添加)OnInitialUpdate中再将其设置为true,然后在OnSize函数中判断m_bInit是否为真。

构造应用程序对象 调用应用程序类的InitInstance函数 SDI 新建 构造文档对象 打开 构造[主/子]框架窗口对象 MDI 构造主框架窗口对象 调用主框架窗口类的PreCreateWindow函数 创建主档框架窗口 调用[主/子]框架窗口类的PreCreateWindow函数 调用主框架窗口类的OnCreate函数 创建[主/子]框架窗口 创建主框架窗口的客户区 调用[主/子]框架窗口类的OnCreate函数 调用主框架窗口类的OnCreateClient函数 创建[主/子]框架窗口的客户区 调用文档类的On[New|Open]Document函数 调用[主/子]框架窗口类的OnCreateClient函数 打开 打开文件并创建存档对象 新建 构造视图对象 调用文档类的Serialize函数 读取文档文件[后关闭文件] 调用视图类的PreCreateWindow函数 文档就绪 调用视图类的 创建视图窗口 OnInitialUpdate函数 调用视图类的OnDraw函数 调用视图类的OnCreate函数 调用视图类的OnSize函数 调用视图类的各种消息响应函数 18

图12-18 创建SDI和MDI应用程序的过程 (实线箭头表示实际过程,虚线箭头表示逻辑过程)

5.例子

下面是一个如何在OnSize中正确使用文档和窗口对象的例子代码框架:

class CTestView : public CView { }

CTestView::CTestView() { }

void CTestView::OnInitialUpdate() { }

void CTestView::OnSize(UINT nType, int cx, int cy) { }

CView::OnSize(nType, cx, cy); // TODO: 在此处添加消息处理程序代码

if (m_Init) …… // 使用文档和窗口对象的代码 CView::OnInitialUpdate();

// TODO: 在此添加专用代码和/或调用基类 m_Init = true; ……

// TODO: 在此处添加构造代码 m_Init = false; ……

bool m_Init; ……

12.2.6 滚动和编辑视图类

除了标准的视图类外,MFC还提供了许多其他功能更强大的视图类,参见图12-13。由于篇幅的限制,这里只简单介绍可自动支持窗口滚动的视图类CScrollView和能自动支持文

19

本文件的读写和编辑的CEditView类。

其他视图类,如CRichEditView(富文本编辑视图)、CHtmlView(网页浏览器视图)、CFormView(窗体视图)和MFC 8.0新加的CWndFormsView(Windows窗体视图)等,已经超过本书的范围,有兴趣的同学,可以自己找资料看。

1.CScrollView类

如果文档较大(如大尺寸图像),不能一次在视图窗口中完整显示,则必须使用滚动窗口。此时,用户视图类必须以CView的派生类CScrollView为基类,该类可以自动支持文档的滚动。

滚动视图类CScrollView是MFC提供的一种自动化程度非常高的滚动窗口类,可在创建项目时,在“MFC应用程序向导”最后一步的“生成的类”页,将C*View的基类从默认的CView改成CScrollView。

对已经存在的项目,可在*View.h和*View.cpp文件中,将所有的CView全部替换成CScrollView。替换方法和步骤:可以选“编辑\\查找和替换\\在文件中替换”菜单项或按“Ctrl+Shift+H”组合键,打开“查找和替换”对话框,在“查找内容”栏中输入“CView”、在“替换为”栏中输入“CScrollView”,保持“查找范围”栏的“整个解决方案”选项不变,按该对话框右下角的“全部替换”按钮。

在OnInitialUpdate函数中或其他需要的地方调用CScrollView类的成员函数SetScrollSizes来设置滚动的范围和参数,该函数的原型为:

void SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault,

const SIZE& sizeLine = sizeDefault );

例如(其中m_iWidth、m_iHeight为视图类的整型类变量,m_bInit为视图类的布尔型类变量(初始化为false);img为文档类中定义的CImage对象,在文档类的序列化函数中装入图像文件,参见9.3.4小节2.中的例子):

void CImageView::OnInitialUpdate() {

CScrollView::OnInitialUpdate(); CImageDoc* pDoc = GetDocument(); // 对滚动视图类,必须设置滚动的尺寸 if(pDoc->img.IsNull())

20

}

SetScrollSizes(MM_TEXT, CSize(100, 100));

else { }

m_iWidth = pDoc->img.GetWidth(); m_iHeight = pDoc->img.GetHeight(); RECT crect; GetClientRect(&crect);

SetScrollSizes(MM_TEXT, CSize(m_iWidth, m_iHeight),

CSize(crect.right, crect.bottom), CSize(10, 10));

//GetParentFrame()->RecalcLayout(); // 对SDI ResizeParentToFit( ); m_bInit = true;

还可以在用户改变窗口大小时,调整滚动的页面和行的大小。例如:

void CImageView::OnSize(UINT nType, int cx, int cy) { }

在OnDraw函数中,可以对整个滚动范围绘图;例如:

void CImageView::OnDraw(CDC* pDC) {

CScrollView::OnSize(nType, cx, cy); // TODO: 在此处添加消息处理程序代码 CImageDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

if (!pDoc->img.IsNull()) { }

if (m_bInit) // 因为创建视图类时会在先调用OnSize后,再调用

// OnInitialUpdate。所以,m_iWidth和m_iHeight的值, // 只能在初始化完成后才能使用。

SetScrollSizes(MM_TEXT, CSize(m_iWidth, m_iHeight),

CSize(cx, cy), CSize(10, 10));

CImageDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

21

}

if (!pDoc)

return;

// TODO: 在此处为本机数据添加绘制代码

if(m_bInit) pDoc->img.BitBlt(pDC->m_hDC, 0, 0);

还可以调用CDC类的成员函数GetClipBox,来确定当前客户区中需要更新的矩形区域及需要绘制的文档范围,该函数的原型为:

virtual int GetClipBox( LPRECT lpRect ) const;

2.CEditView类

CEditView类是另一个常用的CView的派生类,它可以使你的应用程序成为一个简单的文本编辑器,支持文本文件的读写和各种编辑操作(包括基于剪接板的复制),参见图12-19。具体做法与CScrollView类似,只需在创建项目时,将C*View的基类从默认的CView改成CEditView即可。另外,还需要将项目的字符集属性,设置或修改为“使用多字节字符集”,不然在打开ASCII码文件时会出现乱码。

图12-19 编辑视图程序

MFC会在文档类的序列化函数中自动加上如下语句:

void CEditDoc::Serialize(CArchive& ar) {

// CEditView 包含一个处理所有序列化的编辑控件

reinterpret_cast(m_viewList.GetHead())->

SerializeRaw(ar);

}

22

对已经存在的项目,可在*View.h和*View.cpp文件中,将所有的CView替换成CEditView,并将文档类序列化函数中内容替换成如上语句。以CView为基类的默认Serialize函数体为:

void CTestDoc::Serialize(CArchive& ar) { }

if (ar.IsStoring()) { } else { }

// TODO: 在此添加加载代码 // TODO: 在此添加存储代码

12.3 文档窗口

在MFC的文档-视图结构中,一般一个文档对应一个视图窗口。不过我们可以将一个视图窗口拆分成多个窗格(Pane),在不同的窗格中显示文档的不同部分或不同表示。

MDI应用程序在启动运行时,默认会自动创建一个空白的子框架窗口,可以通过在适当的位置添加语句予以屏蔽。

MDI应用程序,一般会在用户选中了“文件”菜单中的“新建”和“打开”菜单项后,自动创建新的文档子窗口或新的选项卡窗口。我们也可以编写代码,在需要时候由自己来创建文档子窗口。我们还可以设置文件过滤器,只打开具有指定扩展名的文件。另外,我们也能根据需要,改变文档窗口的位置和大小。

12.3.1 拆分窗口*

可以将一个视图窗口拆分成多个窗格(参见图12-20),每个窗格显示文档的一个视图,这样一个文档窗口就可以显示多个视图。

23

图12-20 窗口拆分成多个窗格

1.CSplitterWnd类

拆分窗口需要用到MFC的CSplitterWnd类,它是CWnd的派生类:

CObject → CCmdTarget → CWnd → CSplitterWnd

CSplitterWnd类的常用成员函数有: ? 默认构造函数:CSplitterWnd( );

? 创建含有指定行列数窗格的动态拆分窗口:

virtual BOOL Create( CWnd* pParentWnd, int nMaxRows, int nMaxCols, SIZE

sizeMin, CCreateContext* pContext, DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | SPLS_DYNAMIC_SPLIT, UINT nID = AFX_IDW_PANE_FIRST );

? 创建含有指定行列数窗格的静态拆分窗口:

virtual BOOL CreateStatic( CWnd* pParentWnd, int nRows, int nCols, DWORD

dwStyle = WS_CHILD | WS_VISIBLE, UINT nID = AFX_IDW_PANE_FIRST );

? 创建一个窗格,指定初始大小:

virtual BOOL CreateView( int row, int col, CRuntimeClass* pViewClass, SIZE sizeInit,

CCreateContext* pContext );

? 返回当前窗格的行号(从0开始计数):int GetRowCount( ) const; ? 返回当前窗格的列号(从0开始计数):int GetColumnCount( ) const; ? 获取指定列的当前与最小宽度:

void GetColumnInfo( int col, int& cxCur, int& cxMin ) const; ? 获取指定行的当前与最小高度:

void GetRowInfo( int row, int& cyCur, int& cyMin ) const;

? 设置指定行的当前与最小高度:void SetRowInfo( int row, int cyIdeal, int cyMin );

24

? 设置指定列的当前与最小宽度:void SetColumnInfo( int col, int cxIdeal, int cxMin ); ? 在调整窗格大小后重新显示:virtual void RecalcLayout( );

? 获取指定(行/列)窗格的窗口对象:CWnd* GetPane( int row, int col ) const; ? 删除指定行的窗格:virtual void DeleteRow( int rowDelete ); ? 删除指定列的窗格:virtual void DeleteColumn( int colDelete );

2.动态拆分

动态拆分指拆分窗格的数量可以由用户动态改变。在动态拆分的情形下,MFC规定各个窗格所对应的必须是同一个视图类。因此,这种拆分主要用于,在同一个大文档(如大图片、大文本文件)的同一种显示方式中,对文档不同部分的多视,便于对文档进行查看、比较和编辑。

动态拆分的典型例子,是Word窗口的垂直拆分:可以在同一Word窗口的上下窗格中,同时显示文档的不同部分。例如,在上部窗格中显示文档的开始部分,而在下部窗格中显示文档的末尾部分。而Word文档结构图,则是典型的窗口左右水平拆分。

1)使用方法

创建动态拆分应用程序的最简单方法,就是在创建项目时,设置拆分窗口选项。具体做法是,在“MFC应用程序向导”的“用户界面功能”页中,选中“拆分窗口”多选框,参见图12-21。

图12-21 选中“拆分窗口”多选框

25

则MFC会在主(SDI)或子(MDI)框架窗口类的头文件中,自动添加成员变量:

// 属性 protected:

CSplitterWnd m_wndSplitter;

和重写型消息响应成员函数:

// 重写 public:

virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs,

CCreateContext* pContext);

在框架窗口类的代码文件中,自动添加该函数的函数体:

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/, }

其中,默认的最大窗格数为2×2 = 4,默认的最小窗格大小为10×10。这些都可以手工进行调整和修改,例如将窗格的行列数改为“2, 1,”,则最多只能创建2×1 = 2个(上下垂直划分的)窗格。

MFC还会在程序的“视图”菜单中自动添加“拆分”菜单项,用户选中该菜单项后,就可以用鼠标动态确定窗格数目和大小。

2)例子

为了说明动态拆分窗口的具体步骤和应用,我们创建一个名为DSplit的带滚动视图的传统SDI MFC应用程序。设置其滚动窗口的总体大小为2000×2000,并在窗口四角输出表示当前位置的文本串。

具体步骤为:

? 创建一个名为DSplit的带滚动视图的传统单文档MFC应用程序,注意:

? 在“MFC应用程序向导”对话框中的“用户界面功能”页中,选中“拆分窗

口”多选框,参见图12-18。

26

CCreateContext* pContext) { return m_wndSplitter.Create(this,

2, 2, // TODO: 调整行数和列数 CSize(10, 10), // TODO: 调整最小窗格大小 pContext);

? 在“生成的类”页中,将视图类CDSplitView的基类,改为CScrollView,参

见图12-22。

图12-22 修改视图类的基类为“CScrollView”

? 在视图类的初始化函数OnInitialUpdate中,将原来100×100的滚动窗口的整体大

小修改为2000×2000:

void CDSplitView::OnInitialUpdate() { }

? 在视图类的OnDraw函数体中,去掉输入参数中的“/*”和“*/”,并添加若干文本

输出语句:

void CDSplitView::OnDraw(CDC* pDC) {

CScrollView::OnInitialUpdate(); CSize sizeTotal;

// TODO: 计算此视图的合计大小

sizeTotal.cx = sizeTotal.cy = 2000; SetScrollSizes(MM_TEXT, sizeTotal);

CDSplitDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return;

// TODO: 在此处为本机数据添加绘制代码

27

}

// 默认为“TA_LEFT | TA_TOP”

pDC->TextOut(10, 10, L\这是窗口左上角\pDC->SetTextAlign(TA_BOTTOM);

pDC->TextOut(10, 1990, L\这是窗口左下角\pDC->SetTextAlign(TA_RIGHT);

pDC->TextOut(1990, 10, L\这是窗口右上角\pDC->SetTextAlign(TA_BOTTOM | TA_RIGHT); pDC->TextOut(1990, 1990, L\这是窗口右下角\

? 编译、连接、运行该程序

开始时,只有一个窗格,参见图12-23 a);选择“视图”菜单中的“拆分”菜单项,参见图12-23 b);用鼠标动态设置窗格的数目和大小,参见图12-23 c);滚动各个窗格(上窗格到顶、下窗格到底、左窗格到左端、右窗格到右端),就可以同时看到位于窗口四角的4个文本串,参见图12-23 d)。

a)

b)

c)

d)

图12-23 拆分窗口例

28

3.静态拆分

静态拆分指拆分窗格的数量从一开始就是固定的,不能由用户动态改变。在静态拆分的情形下,每个窗格可以对应不同的视图类。因此,这种拆分主要用于,对同一个文档的不同显示方式的多视。

例如,对于一个图片文件,可以在一个窗格显示图片,在另一个窗格显示文件的二进制内容。又例如,对一个数值数据文件,可以在一个窗格显示数据表格,在另一个窗格显示数据所对应的图形(如曲线、条状图或饼图)。

1)使用方法

为了实现静态多视,可以在(SDI的主 / MDI的子)框架窗口类中定义一个CSplitterWnd对象的类变量,例如:

CSplitterWnd m_wndSplitter;

然后为框架窗口类添加(重写型)消息响应函数OnCreateClient,在该函数中调用CSplitterWnd 类的CreateStatic和CreateView函数来创建拆分窗口。例如:

m_wndSplitter.CreateStatic(this, 2, 2);

m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CMy1View),

CSize(200, 100), pContext);

m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CMy2View),

CSize(400, 100), pContext);

m_wndSplitter.CreateView(1, 0, RUNTIME_CLASS(CMy3View),

CSize(200, 200), pContext);

m_wndSplitter.CreateView(1, 1, RUNTIME_CLASS(CMy4View),

CSize(400, 200), pContext);

return TRUE; // 注意:必须返回TRUE

还需要为项目添加若干新的视图类(从C[Scroll]View派生),并为每个新的视图类添加GetDocument成员函数(包括内联版和调试版),以返回对应的文档类对象。

最后就可以在各个视图类里绘制和输出同一文档数据所对应的不同图形和文本了。 2)例子

编写一个具有拆分窗口的SDI型图像显示程序。文档窗口被垂直拆分成两个窗格,左边窗格显示各向同性(保持原图像的长宽比)的(小)图像,右边窗格显示各向异性(不保

29

持原图像的长宽比)的(大)图像(充满窗格),参见图12-24。图像显示使用CImage类。

图12-24 具有静态拆分窗口的图像显示程序

下面是主要的编程步骤(粗体部分为自己添加的):

(1)创建一个名为SSplit的SDI型传统MFC应用程序,在主框架类中添加拆分窗口类对象的类变量m_wndSplitter:

class CMainFrame : public CFrameWnd { …… // 属性 public: …… }

(2)为主框架窗口类和(重写型)消息响应函数OnCreateClient(注意必须返回TRUE),需要添加对应的头文件包含(不然编译器会找不到相关的视图类):

#include \#include \#include \……

BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs,

CCreateContext* pContext) {

// TODO: 在此添加专用代码和/或调用基类 m_wndSplitter.CreateStatic(this, 1, 2); CSplitterWnd m_wndSplitter;

30

m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CSSplitView),

CSize(200, 400), pContext);

m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CStretchView),

CSize(400, 400), pContext);

}

return TRUE; //CFrameWnd::OnCreateClient(lpcs, pContext);

(3)为SSplit项目添加基类为CView的视图类CStretchView,并为该视图类添加GetDocument成员函数(包括内联版和调试版),以返回对应的文档类对象:

? StretchView.h:

#pragma once

#include \// CStretchView 视图

class CStretchView : public CView {

…… public:

CSSplitDoc* GetDocument() const; …… };

#ifndef _DEBUG // StrechView.cpp 中的非调试版本

inline CSSplitDoc* CStretchView::GetDocument() const { return reinterpret_cast(m_pDocument); } #endif ? StretchView.cpp:

……

#ifdef _DEBUG ……

CSSplitDoc* CStretchView::GetDocument() const {// 内联调试版本

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CSSplitDoc))); return (CSSplitDoc*)m_pDocument;

31

}

#endif //_DEBUG

// CStretchView 消息处理程序 ……

(4)在文档类中定义CImage对象:

#pragma once

#include

class CSSplitDoc : public CDocument { …… // 属性 public: …… };

(5)在文档类的Serialize函数中装入图像:

void CSSplitDoc::Serialize(CArchive& ar) { }

(6)在CSSplitView类的OnDraw函数中绘制各向同性的图像:

void CSSplitView::OnDraw(CDC* pDC) {

CImage img;

if (ar.IsStoring()) { } else { }

// TODO: 在此添加加载代码

if (!img.IsNull()) img.Destroy();

HRESULT hr = img.Load(ar.GetFile()->GetFilePath()); ASSERT(SUCCEEDED(hr)); // TODO: 在此添加存储代码

CSSplitDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

32

}

if (!pDoc) return;

// TODO: 在此处为本机数据添加绘制代码 if (!pDoc->img.IsNull()) { }

RECT rect;

GetClientRect(&rect);

if (pDoc->img.GetWidth() > rect.right) { }

else pDoc->img.Draw(pDC->m_hDC, 0, 0);

rect.bottom = (int)(((float)pDoc->img.GetHeight()

* rect.right) / pDoc->img.GetWidth() + 0.5);

pDC->SetStretchBltMode(HALFTONE); pDoc->img.Draw(pDC->m_hDC, rect);

(7)在CStretchView类的OnDraw函数中绘制各向异性的图像:

void CStretchView::OnDraw(CDC* pDC) { }

CSSplitDoc* pDoc = GetDocument(); // TODO: 在此添加绘制代码 if (!pDoc->img.IsNull()) { }

RECT rect;

GetClientRect(&rect);

pDC->SetStretchBltMode(HALFTONE); pDoc->img.StretchBlt(pDC->m_hDC, rect);

12.3.2 MDI程序不自动创建子窗口

为了MDI程序在开始运行时不自动创建一个新文档及其对应子窗口,可在应用程序类C*App的InitInstance函数的语句

33

// 分析标准外壳命令、DDE、打开文件操作的命令行 CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo);

// 调度在命令行中指定的命令。如果

// 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,

则返回 FALSE。

if (!ProcessShellCommand(cmdInfo))

return FALSE;

之间,加上语句:

// 不让在启动时创建子框架窗口

cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;

12.3.2 自己创建文档窗口*

通常MFC只在响应ID_FILE_NEW和ID_FILE_OPEN消息时才创建文档窗口,若要在MDI应用程序的视图类中,在自己需要的时候创建同类的文档窗口,可以分如下三步进行。

1.获得文档模板

可先用文档类的成员函数GetDocTemplate来获得视图所对应的文档模板:

CDocTemplate* GetDocTemplate( ) const;

2.创建新子框架窗口

再调用文档模板类的成员函数CreateNewFrame来创建新子框架窗口:

virtual CFrameWnd* CreateNewFrame( CDocument* pDoc, CFrameWnd* pOther );

其中,pDoc为本视图类所对应的文档类对象的指针,pOther为MDI文档的子框架窗口的指针,可用视图类的基类CWnd的成员函数GetParentFrame来获得:

CFrameWnd* GetParentFrame( ) const;

34

3.初始化子框架窗口

然后再调用文档模板类的成员函数InitialUpdateFrame:

virtual void InitialUpdateFrame( CFrameWnd* pFrame, CDocument* pDoc,

BOOL bMakeVisible = TRUE );

来初始化并显示子框架窗口。

4.例子

在自己添加的菜单项ID_DOC_NEW的事件处理函数中自己来创建文档窗口:

void CTestView::OnDocNew() { }

CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc);

CMDIChildWnd* pChild = (CMDIChildWnd*)GetParentFrame( ); CDocTemplate* pTem = pDoc->GetDocTemplate(); ASSERT_VALID(pTem);

CFrameWnd* pFWnd = pTem->CreateNewFrame(pDoc, pChild); pTem->InitialUpdateFrame(pFWnd, pDoc);

12.3.3 文件过滤器

文件过滤器用于指定打开和保存文件的扩展名,可以通过修改资源串、设置多文档模板、或添加自己的OnFileOpen消息响应函数(在其中,利用指定的过滤串,打开公用文件对话框)等方法,来为MFC应用程序设置文件过滤器。

1.资源串

通过资源串设置文件过滤器,可以在创建项目时进行,也可以在创建项目后再修改。 1) 创建时设置

在创建(非对话框类型的)项目时,在“MFC应用程序向导”对话框的“文档模板字符串”页中进行如下修改和设置(参见图12-25,结果如图12-26 所示):

35

图12-25 设置文档模板字符串

图12-26 图片文件过滤器

? “文件扩展名”编辑框中键入文件扩展名,如:

? 单个扩展名:bmp。

? 多个扩展名:bmp;.gif;.jpg(用分号分割,注意从第2个扩展名起,前面必须

加句点符“.”)。

? 在“文件类型名称”编辑框中键入标识应用程序的文档可归入的文档类型,如“位

图文件”,默认为项目名。

? 在“筛选器名”编辑框中键入(用户可以用来指示查找特定文件类型的文件的名称

的)文件类型的描述,如:“位图文件(*.BMP)”。默认为“项目名 Files (*.扩展名)”,

36

例如,若项目名为“Image”,文件扩展名为“bmp”,则默认的筛选器名就为“Image Files (*.bmp)”。对多个扩展名,需要在第1个扩展名之外的其他扩展名的句点前,手工添加星号符“*”,如“图片文件 (*.bmp;*.gif;*.jpg)”,参见图12-25。 该对话框页中的其他项目的含义如下:

? “文件类型 ID ”——用于在系统注册表中设置文档类型的标签,默认为“项目

名.Document”。

? “语言”——指示为“本地化字符串”下的所有框显示字符串所使用的语言。若要

更改此框中的值,需在 “MFC 应用程序向导”对话框的“应用程序类型”页的“资源语言”下拉式列表中选择合适的语言。

? “主框架标题”——设置出现在主应用程序框架窗口顶部标题条的文本串,默认为

项目名。

? “文件的新简称”——如果有不止一个新文档模板,设置出现在标准 “新建”对

话框中的名称,默认为项目名。

? “文件类型全名”——在系统注册表中设置文件类型名称。默认为“项目

名.Document”。 2) 创建后修改

可通过修改资源视图页的“项目名\\项目名.rc\\String Table\\String Table”串表资源中的ID:IDR_MAINFRAME(SDI)或IDR_MyType(MDI)所对应的串,为应用程序的文件I/O对话框增加文件过滤器。如为Wave程序增加*.wav的过滤器:

将原来的串

SDI:IDR_MAINFRAME“Wave\\n\\nWave\\n\\n\\nWave.Document\\nWave Document” MDI:IDR_WaveTYPE“\\nWave\\nWave\\n\\n\\nWave.Document\\nWave Document” 修改成

SDI:“Wave\\n\\nWave\\n波形文件(*.wav)\\n.wav\\nWave.Document\\nWave Document” MDI:“\\nWave\\nWave\\n波形文件(*.wav)\\n.wav\\nWave.Document\\nWave Document” 其中(每个C++转义字符“\\n”表示一行):

? 第1行“Wave\\n”为应用程序的窗口名。

? 第2行“\\n”为默认文档名的根(若该行为空,则默认文档名的根为“未命名”或

Untitled)。

? 第3行“Wave\\n”为文档类型名。

37

? 第4行“波形文件(*.wav)\\n”为文档类型和过滤器的描述。 ? 第5行“.wav\\n”为过虑器本身(文件扩展名),注意前面有句点。 ? 第6行“Wave.Document”为注册的文件类型的ID ? 第7行“Wave Document”为注册的文件类型的描述

上面讨论的是单个扩展名的情况,也可以设置多个扩展名。例如为Image程序增加多种图像格式的文件过滤器:

SDI:“Image\\n\\nImage\\n图片文件 (*.bmp;*.gif;*.jpg)\\n.bmp;.gif;.jpg\\nImage.Document\\n Image Document”

MDI:“\\nMy\\nImage\\n图片文件 (*.bmp;*.gif;*.jpg)\\n.bmp;.gif;.jpg\\nImage.Document\\n Image.Document”

上面的字符串修改方法,也可以在项目的*.rc源文件中的字符串表部分手工进行。 注意:上面的几个资源串方法都不支持多个文件过虑器(上面只有支持多个扩展名的单个[复合]滤波器),为了同时支持多个滤波器(例如,在图像程序中,可以同时添加多个分别单独处理*.BMP、*.GIF、*.JPG等文件的单扩展名滤波器、以及多个可以处理多个扩展名的多扩展名复合滤波器),可采用下面介绍的两种方法。

2.多文档模板*

在多文档程序的C*App类的InitInstance函数中创建多文档模板类CMultiDocTemplate的对象:

CMultiDocTemplate( UINT nIDResource, CRuntimeClass* pDocClass,

CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );

并使用CWinAPP的成员函数AddDocTemplate:

void AddDocTemplate( CDocTemplate* pTemplate );

将新建的模板加入。如:

pDocTemplate = new CMultiDocTemplate(

IDR_BMPTYPE,

RUNTIME_CLASS(CImageDoc),

RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CImageView));

38

AddDocTemplate(pDocTemplate);

pDocTemplate = new CMultiDocTemplate(

IDR_GIFTYPE,

RUNTIME_CLASS(CImageDoc),

RUNTIME_CLASS(CChildFrame), // 自定义 MDI 子框架 RUNTIME_CLASS(CImageView));

AddDocTemplate(pDocTemplate);

其中,IDR_BMPTYPE与IDR_GIFTYPE为资源ID,对应的有与1.类似的串资源,还有菜单资源等。

3.OnFileOpen

如果只是文件过虑器不同,而菜单、文档类、子框架类及视图类都一样的话,则上面方法的编程必须同时维护几个完全相同的菜单,太低效。解决办法是添加自己的OnFileOpen消息响应函数。

具体步骤为:

? 覆盖应用程序类中对ID_FILE_OPEN消息的默认响应CWinApp::OnFileOpen:

//ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

? 为应用程序类添加自己的ID_FILE_OPEN事件处理函数。

? 在自己的OnFileOpen函数中,设置多个文件过虑器,并用其调用文件公用对话框。 ? 用用户选择的完整文件路径名调用CWinApp的成员函数OpenDocumentFile:

virtual CDocument* OpenDocumentFile( LPCTSTR lpszFileName );

? OpenDocumentFile函数,会使MFC系统继续应用程序的文档视图创建过程。 例如(参见图12-27):

void CImageApp::OnFileOpen() {

wchar_t filters[] = L\图像文件(*.bmp;*.gif;*.jpg;*.png;*.tif) \\|*.bmp;*.gif;*.jpg; *.png;*.tif|位图文件(*.bmp)|*.bmp|图形交换\\格式文件(*.gif)|*.gif|联合图象专家组[JPEG]文件(*.jpg)|*.jpg|可移\\植网络图形文件(*.png)|*.png|标记图像文件格式[TIFF]\\ 文件(*.tif)

39

\\|*.tif|所有文件(*.*)|*.*||\

CFileDialog fileDlg(TRUE, NULL, NULL, OFN_HIDEREADONLY,

}

filters);

if (fileDlg.DoModal() == IDOK)

OpenDocumentFile(fileDlg.GetPathName());

图12-27 具有多个文件过虑器的打开文件对话框

以上的各种方法中,最常用的是:

? 添加单个文件滤波器(包括支持多个文件扩展名的单个复合滤波器)——创建时设置文件扩展名,或在创建后修改资源串。

? 添加多个文件滤波器——添加自己的 OnFileOpen消息响应函数。 而通过创建多个文档模板,来添加多个文件滤波器的方法,则很少被使用。

12.3.4 改变文档窗口的大小和位置*

有时需要根据文档(如图像)的尺寸来调整子窗口的大小,如使用MDI来实现BMP、GIF和JPG等图像的显示。

40

本文来源:https://www.bwwdw.com/article/jiag.html

Top