1、文档视结构程序实例文档视结构程序实例下面,我们以一个简单的文本编辑器为例,说明文档/视结构的原理及应用。由于我们重在讨论文档/视结构而不是编辑器的实现,因此这个编辑器设计的非常简单:用户只能逐行输入字符,以回车结束一行并换行,不支持字符的删除和插入,也没有光标指示当前编辑位置。另外,用户可以选择编辑器显示文本时所使用的字体。图7-4首先,使用AppWizard生成编辑器程序的框架:在New对话框的Project Name编辑框中输入项目名为Editor。在AppWizard的第一步选择Single document ,这将创建一个SDI应用程序。AppWizard第二和第三步选项使用缺省值。在
2、AppWizard Step 4 of 6对话框中,如图7-4所示,细心的读者或许会注意到在这一页里,有一个Advanced按钮,以前没有提到过。现在揿击该按钮,弹出Advanced Option对话框,如图7-5所示。Advanced Option对话框是用来设置文档视结构和主框架窗口的一些属性的。图7-5该对话框提供两个标签页,一页是Document Template String(文档模板字符串,有关文档模板字符串,我们还将在后面作详细介绍),用于设置文档视结构的一些属性。它包括以下几个编辑框:File Extension:指定应用程序创建的文档所用的文件名后缀。输入后缀名txt(不需要
3、号。),表明Editor使用文本文件的后缀名TXT。File ID:用于在Windows95的注册数据库中标识应用程序的文档类型。MainFrame Caption:主框架窗口使用得标题,缺省情况下与项目名相一致,你当然可以将它改为任何你喜欢的名字,如Editor for Windows等。Doc Type name:文档类型名,指定与一个从CDocument派生的文档类相关的文档类型名。Filter Name:用作“打开文件”、“保存文件”对话框中的过滤器。当你在File Extension中输入后缀名是,Visual Studio会自动给你生成一个过滤器:Editor Files(*.tx
4、t)。这样,当你在Open File对话框中选择Editor Files(*.txt)时,只有以txt为后缀名的文件名显示在文件名列表中。File new name(short name):用于指定在new对话框中使用的文档名。当应用程序支持多种文档类型时,选择File-New菜单项会弹出一个对话框,列出应用程序所支持的所有文档类型,供用户选择。选择一种文档类型后,自动创建相应类型的文档。这里我们只支持编辑器这一种文档类型,故使用缺省值。File Type name(long name):用于指定当应用程序作为OLE Automation服务器时使用的文档类型名。使用缺省值。 另一页是Wind
5、ow Styles,用于设置主框架窗口的一些属性,包括框架窗口是否使用最大化按钮、最小化按钮,窗口启动时是否最大化或最小化等。这里我们使用缺省值,不需要作任何修改。按OK按钮,关闭Advanced Option对话框。AppWizard后面的几页对话框都使用缺省值。创建完Editor框架程序后,Visual Studio自动打开Editor工程。现在要修改Editor框架程序,往程序中添加代码,实现编辑器功能。7.2.1 文档/视结构中的主要类在Editor框架程序中,与文档视结构相关的类有CEditorApp、CMainFrame、CEditorView和CEditorDoc,它们分别是应用
6、程序类CWinApp、框架窗口类CFrameWnd、视图类CView和文档类CDocument的派生类。应用程序对象其中,应用程序类负责一个且唯一的一个应用程序对象的创建、初始化、运行和退出清理过程。如果在AppWizard生成框架时指定使用单文档或多文档,AppWizard会自动将File菜单下的New、Open和Printer Setup(打印机设置)自动映射到CWinApp的OnFileNew、OnFileOpen、OnFilePrintSetup成员函数,让CWinApp来处理以上这些消息。如清单7.1,浏览CEditorApp类的定义文件有关消息映射的代码。清单7.1 CEditor
7、App的消息映射BEGIN_MESSAGE_MAP(CEditorApp, CWinApp)/AFX_MSG_MAP(CEditorApp)ON_COMMAND(ID_APP_ABOUT, OnAppAbout)/ NOTE - the ClassWizard will add and remove mapping macros here./ DO NOT EDIT what you see in these blocks of generated code!/AFX_MSG_MAP/ Standard file based document commandsON_COMMAND(ID_FIL
8、E_NEW, CWinApp:OnFileNew)ON_COMMAND(ID_FILE_OPEN, CWinApp:OnFileOpen)/ Standard print setup commandON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp:OnFilePrintSetup)END_MESSAGE_MAP()这表明,框架已经给我们生成了有关新建文档、打开文档以及打印设置的标准代码,我们不必再去做这些重复的工作了。那么,当我们新建或打开一个文档时,应用程序怎么知道要创建什么样的文档以及创建什么样的视图、框架窗口来显示该文档的呢?在文档/视结构中,应用程序通过为
9、应用程序所支持的每一种文档创建一个文档模板,来创建和管理所有的文档类型并为它们生成相应的视图和框架窗口。文档模板文档模板负责创建文档、视图和框架窗口。一个应用程序对象可以管理一个或多个文档模板,每个文档模板用于创建和管理一个或多个同种类型的文档(这取决于应用程序是单文档SDI程序还是多文档MDI程序)。那些支持多种文档类型(如电子表格和文本)的应用程序,有多种文档模板对象。应用程序中的每一种文档,都必需有一种文档模板和它相对应。比如,如果应用程序既支持绘图又支持文本编辑,就需要一种一种绘图文档模板和文本编辑模板。在下一章里,我们举了一个这样的例子,来说明多种文档模板的实现技术。MFC提供了一个
10、文档模板类CDocTemplate支持文档模板。文档模板类是一个抽象的基类,它定义了文档模板的基本处理函数接口。由于它是一个抽象基类,因此不能直接用它来定义对象而必需用它的派生类。对一个单文档界面程序,使用CSingleDocTemplate(单文档模板类),而对于一个多文档界面程序,使用CMultipleDocTemplate。文档模板定义了文档、视图和框架窗口这三个类的关系。通过文档模板,我们可以知道在创建或打开一个文档时,需要用什么样的视图、框架窗口来显示它。这是因为文档模板保存了文档和对应的视图和框架窗口的CRuntimeClass对象的指针。此外,文档模板还保存了所支持的全部文档类的
11、信息,包括这些文档的文件扩展名信息、文档在框架窗口中的名字、代表文档的图标等信息。提示:每个从CObject派生的类都与一个CRuntimeClass结构相关联。通过这个结构,你可以在程序运行时刻获得关于一个对象和它的基类的信息。在函数参数需要作附加类型检查时,这种运行时刻判别对象类型的能力是非常重要的。C+本身并不支持运行时刻类信息。CRuntimeClass结构包含一个以0结尾的字符串类名、整型的该类对象大小、基类的运行时刻信息等。 一般在应用程序的InitInstance成员函数实现中创建一个或多个文档模板,如清单7.2。清单7.2 CEditorApp的InitInstance成员函数
12、定义BOOL CEditorApp:InitInstance()/标准的初始化代码/./ Register the applications document templates. Document templates/ serve as the connection between documents, frame windows and views.CSingleDocTemplate* pDocTemplate;pDocTemplate = new CSingleDocTemplate(IDR_MAINFRAME,RUNTIME_CLASS(CEditorDoc),RUNTIME_CLA
13、SS(CMainFrame), / main SDI frame windowRUNTIME_CLASS(CEditorView);AddDocTemplate(pDocTemplate);/其他的初始化代码和主框架窗口显示过程/./ Enable DDE Execute openEnableShellOpen();RegisterShellFileTypes(TRUE);/ Parse command line for standard shell commands, DDE, file openCCommandLineInfo cmdInfo;ParseCommandLine(cmdInf
14、o);/ Dispatch commands specified on the command lineif (!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();/ Enable drag/drop openm_pMainWnd-DragAcceptFiles();在InitInstance中,首先声明一个CS
15、ingleDocTemplate*类型的单文档模板对象指针(因为这里的文本编辑器使用单文档界面)。然后创建该类型的模板对象。如果要使用多文档界面,只需要将这里的CSingleDocTemplate改为CMultiDocTemplate,当然CMainFrame也要改为从CFrameWnd改为CMDIChildWnd或其派生类。在CSingleDocTemplate构造函数中,还包含一个IDR_MAINFRAME参数。它指向一个字符串资源,这个字符串给出了文档所使用及显示时所要求的几个选项,包括文档名字、文档的文件扩展名、在框架窗口上显示的名字等等,我们称之为文档模板字符串。有关文档模板字符串还
16、将在下一章使用多个文档模板这一节作详细阐述,因此这里就不展开讲了。然后InitInstance调用AddDocTemplate将创建好的文档模板加入到应用程序可用的文档模板链表中去。这样,如果用户选择了File-New或File-Open菜单要求创建或打开一个文档时,应用程序类的OnNewDocument成员函数和OnOpenDocument()成员函数就可以从文档模板链表中检索出文档模板提示用户选择适当的文档类型并创建文档及相关的视图、框架窗口。文档Editor的文档类CEditorDoc从CDocument派生下来,它规定了应用程序所用的数据。如果需要在应用程序中提供OLE功能,则需要从C
17、OleDocument或其派生类派生出自己的文档类。视图Editor的视图类从CView派生,它是数据的用户窗口。视图规定了用户查看文档数据以及同数据交互的方式。有时一个文档可能需要多个视图。如果文档需要卷滚,需要从CScrollView派生出视图类。如果希望视图按一个对话框模板资源来布置用户界面,可以从CFormView派生。由于CFormView经常同数据库打交道,因此我们把它放在第十章“数据库技术”中结合数据库技术讲解。感兴趣的读者可以先看看Visual C+ MFC例子CHKBOOK(在SAMPLESMFCGENERALCHKBOOK目录下)。框架窗口视图在文档框架窗口中显示,它是框架
18、窗口的子窗口。框架窗口作用有二:一是为视图提供可视的边框,还包括标题条、一些标准的窗口组件(最大、最小化按钮、关闭按钮),象一个容器一样把视图装起来。二是响应标准的窗口消息,包括最大化、最小化、调整尺寸等。当框架窗口关闭时,在其中的视图也被自动删除。视图和框架窗口关系如图7-6所示:图7-6 视图和框架窗口的关系对于SDI程序,文档框架窗口也就是应用程序的主框架窗口。在MDI应用程序中,文档框架窗口是显示在主框架窗口中的子窗口(通常是CMDIChildWnd或其派生类)。可以从主框架窗口类派生出新类来包含你的视图,并指定框架的风格和其他特征。如果是SDI程序,则从CFrameWnd派生出文档框
19、架窗口:class CMainFrame:public CFrameWnd.;如果是MDI窗口,则需要从CMDIFrameWnd派生出主框架窗口,同时在从CMDIChildWnd或其派生类派生出一个新类,来定制特定文档窗口的属性和功能。在应用程序运行过程中,以上几种类型的对象相互协作,来处理命令和消息。一个且唯一的一个应用程序对象管理一个或多个文档模板,每个文档模板创建和管理一个(SDI)或多个文档(MDI)。用户通过包含在框架窗口中的视图来浏览和操作文档中的数据。在SDI应用程序中,以上对象关系如图7-7所示。图7-7 在SDI程序中各对象的关系7.2.2 设计文本编辑器的文档类弄清这些对象
20、的关系以后,就可以着手往框架里填写代码,实现我们的文本编辑器程序了。从以上分析可以看出,文档视结构程序的主要工作在于文档和视图的设计。首先设计文档。程序=数据+算法,在MFC文档/视结构中,最关键的就是文档的设计。怎样保存用户输入的文本行?方法之一是保存一组指针,每个指针指向一个文本行。如果使用C语言来写这个程序的话,需要分配内存来存放这些指针,还要自己编写文本行的动态分配、增加、删除等例程。但是MFC简化这些工作,它提供了集合类(collection classes)。集合类是用来容纳和处理一组对象或标准数据类型变量的C+类。每个集合类对象可以看作一个单独的对象。类成员函数可作用于集合的所有
21、元素。MFC提供两种类型的集合类:基于模板的集合类非基于模板的集合类这两种集合类 对用户来说非常相似。基于模板的集合所包含的元素是用户自定义的数据结构或者说是抽象的数据结构,它以数组、链表和映射表三种方式组织用户自定义的数据结构。使用基于模板的集合类需要用户作一些类型转换工作。非基于 模板的集合类提供的是一组现成的、用于某种预定义的数据类型(如CObject、WORD、BYTE、DWORD、字符串等)的集合。在设计程序时,如果所用的数据类型是预定义的,如下面的编辑要用到的字符串,则使用非基于模板的集合类;如果所用得数据类型是用户自定义的数据结构类型,那就要用到基于模板的集合类。根据对象在集合中
22、的组织合存储方式,集合类又可分为三种类型:链表、数组、映射(或字典)。应当根据特定的编程问题,选择适当的类型。链表:链表类用双向链表实现有序的、非索引的元素链表。链表有一个头或尾。很容易从头或尾增加或删除元素、遍历所有元素,在中间插入或删除元素。链表在需要增加、删除元素的场合效率很高。非基于模板的链表有三种:CObList、CPtrList、CStringList,分别用于管理对象指针、无类型指针和字符串。可以使用链表创建堆栈和队列。要访问链表的成员,可以使用GetNext和GetHeadPosition()。要删除链表的成员,可以用GetHeadPosition()和GetNext()来遍历
23、链表,然后用delete删除其中的对象,最后调用RemoveAll删除链表所包含的指针。数组类提供一个可动态调整数组大小的、有序的、按整数索引的对象数组。数组在内存中连续的存放固定长度的数组元素。数组的最大优点是可以随时存取任一元素。数组类包括基于模板的CArray,它可以存放任何类型的数据;MFC还为字节、字、双字、CString对象、CObject指针和无类型指针提供了预定义的类。数组的元素可以通过一个以零为基础的整数下标直接进行访问。下标操作符()可用于设置或检取数组元素。如果要设置一个超过数组当前范围的元素,可以指定该数组是否自动增大。但是如果要调整数组大小时,则数组占用的内存块需要重
24、新移动,效率很低。如果不要求调整数组大小,则对数组集合的访问和对标准C数组的访问一样快。在使用数组之前,应使用SetSize建立其大小,并分配内存。若不用SetSize,象数组添加元素时会导致频繁的再分配内存和拷贝数据。数组类适用于那些需要快速检索、很少需要增加或删除元素的集合。数组通过GetAt(索引值)来访问数组中的成员。要删除数组中的成员,可以用GetSize()取得大小,然后遍历数组中成员,用delete删除,然后调用RemoveAll()清除其中的指针数据。下面是使用数组模板类的例子:CArray myArray;CMyClass myClass;myArray-Add(myClas
25、s);映射类以一种字典的方式组织数据。每个元素由一个关键字和一个数值项组成,关键字用作数值项的标识符,在集合中不允许重复,必须是唯一的。如果给出一个关键字,映射类会很快找到对应的数值项。映射查找是以哈希表的方式进行的,因此在映射中查找数值项的速度很快。除了映射类模板外,预定义的映射类能支持CString对象、字、CObject指针和无类型指针。比如,CMapWordToOb类创建一个映射表对象后,就可以用WORD类型的变量作为关键字来寻找对应的CObject指针。映射类最适用于需要根据关键字进行快速检索的场合。要访问映射中的数据,可以用GetStartPosition()定位到开始处,再用Ge
26、tNextAssoc访问映射表中的成员。要删除映射中的数据,可以用GetStartPosition和GetNextAssoc遍历并用delete删除对象,然后调用RemoveAll。下面是使用CMap模板类的例子:CMap myMap;CPerson person;LPCSTR lpstrName=“Tom”;myMap-SetAt(lpstrName,person);有关集合类的使用可以参见MFC的例子COLLECT。对于文本编辑器,由于需要动态增加和删除每一行字符串,因此使用CStringList来保存文本编辑器的数据,CStringList中的每一个元素是CString类型的,它代表一行
27、字符。可以把CString看作一个字符数组,但它提供了丰富的成员函数,比字符数组功能强大的多。另外,还需要增加一个数据成员nLineNum,用于指示当前编辑行行号。如清单7.3,在文档类的头文件EditorDoc.h中,加入以下代码:清单7.3 CEditorDoc.hclass CEditorDoc : public CDocumentprotected: / create from serialization onlyCEditorDoc();DECLARE_DYNCREATE(CEditorDoc)/ Attributespublic:CStringList lines;int nLin
28、eNum;.;在定义了文档数据成员后,还要对文档数据成员进行初始化。初始化文档类的数据成员当用户启动应用程序,或从应用程序的File菜单种选择New选项时,都需要对文档类的数据成员进行初始化。一般的,类的数据成员的初始化都是在构造函数中完成的,在构造函数调用结束时对象才真正存在。但对于文档来说却不同,文档类的数据成员初始化工作是在OnNewDocument成员函数中完成的,此时文档对象已经存在。为什么呢?这是因为:在单文档界面(SDI)应用程序中,在应用程序启动时,文档对象就已经被创建。文档对象直到主框架窗口被关闭时才被销毁。在用户选择File-New菜单时,应用程序对象并不是销毁原来的文档对
29、象然后重建新的文档对象,而只是重新初始化(Re-Initialization)文档对象的数据成员,这个初始化工作就是应用程序对象的OnFileNew()消息处理成员函数通过调用OnNewDocument()函数来完成的。试想,如果把初始化数据成员的工作放在构造函数中的话,由于对象已经存在,构造函数就无法被调用,也就无法完成初始化数据成员的工作。为了避免代码的重复,在应用程序启动时,应用程序对象也是通过调用OnNewDocument成员函数来初始化文档对象的数据成员的。如果是多文档界面(MDI)程序,则数据成员的初始化也可以放到构造函数中完成。因为在MDI中,选择File-New菜单时,应用程序
30、对象就让文档模板创建一个新文档并创建对应的框架窗口和视图。但是,为了保证应用程序在单文档和多文档界面之间的可移植性,我们还是建议将文档数据成员的初始化工作放在OnNewDocument()中完成,因为在MDI的应用程序对象的OnFileNew成员函数中,同样会调用文档对象的OnNewDocument成员函数。在OnNewDocument成员函数中手工加入代码,如清单7.4。清单7.4 OnNewDocument成员函数BOOL CEditorDoc:OnNewDocument()if (!CDocument:OnNewDocument()return FALSE;/ TODO: add rei
31、nitialization code here/ (SDI documents will reuse this document)nLineNum=0;POSITION pos;pos=lines.GetHeadPosition();while(pos!=NULL)(CString)lines.GetNext(pos).Empty();lines.RemoveAll();return TRUE;其中pos类型为POSITION,相当于链表的指针,指向链表当前元素。CStringList的成员函数GetHeadPosition()返回链表头指针。链表的GetNext()函数以当前指针为参数,返回下一个元素指针,同时修改pos,使它指向下一个元素。使用强制类型转换将GetNext()函数返回的元素指针转化为CString类型,然后调用CString:Empty()方法清除该行中的所有字符。通过一个while循环,清除所有文本行的数据。最后调用CStringList的RemoveAll()成员函数,清除链表中的所
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1