Visual C++.NET编程讲座之三

发表于:2007-05-25来源:作者:点击数: 标签:编程C++.NET之三讲座visual
第二讲 文档数据的读取和显示 摘要 本讲先来介绍程序各个类的关联机制,然后讨论文档的读过程及读操作,最后讨论文档数据的显示方法和技巧。 程序中各用户类的关联 在上一讲中,我们使用MFC应用程序向导创建一个单文档项目TextViewer。现在,我们打开该项目
第二讲 文档数据的读取和显示


  摘要


  本讲先来介绍程序各个类的关联机制,然后讨论文档的读过程及读操作,最后讨论文档数据的显示方法和技巧。
  程序中各用户类的关联


  在上一讲中,我们使用MFC应用程序向导创建一个单文档项目TextViewer。现在,我们打开该项目。可以看出,向导为TextViewer项目创建了以下几个类:


  应用程序类CTextViewerApp,应用程序必须的运行入口,在上一讲已经讨论过。


  主框架窗口类CMainFrame,用来负责窗口的标题栏、菜单、工具栏及状态栏等界面元素的操作。


  文档类CTextViewerDoc,用来负责文档数据的读取和保存


  视图类CTextViewerView类,用来显示文档显示,并可响应各种类型的输入(例如键盘输入)以及实现打印和打印预览等。


  还有一个对话框类CAboutDlg,用来显示该应用程序的版本信息,是一个"关于"对话框。


  需要说明的是,Visual C++ .NET将各个类的声明保存在头文件中,即以.h为扩展名,而将类的实现代码保存在以.cpp为扩展名的实现文件中。


  那么,在MFC中上述的主框架窗口类、文档类和视图类的关系是怎样呢?我们来看一下。


  将解决方案管理器切换到"类视图",展开CTextViewerApp类的所有节点,双击"InitInstance( void )",打开该函数代码。由于Visual C++ .NET的代码注释是中文的,因此我们这里仅给出如图1所示的代码。



图1 InitInstance函数中的部分代码


  代码中,CSingleDocTemplate是从CDocTemplate派生的单文档类,它协调了文档窗口、文档和视图的关系,并把三者联系起来。该类的构造函数需要指定四个参数,分别为表示菜单和加速键等的资源ID号以及三个由宏RUNTIME_CLASS指定的CRuntimeClass结构对象指针,它们分别是程序的文档类、框架窗口类和视图类的结构指针。


  CRuntimeClass结构反映一个运行时类的信息,通常用宏RUNTIME_CLASS来获取一个类的CRuntimeClass结构指针。Visual C++借助CRuntimeClass类结构能在应用程序运行过程中获得该类对象及其基类的相关信息,从而可以实现运行时类型检查(Run Time Type Inspection,RTTI)。


  AddDocTemplate用来将指定的单文档模板或多文档模板指针添加到程序所包含内部的文档模板指针列表中。





  文档的读过程


  在向导创建的应用程序中,程序的默认菜单有"文件"、"编辑"、"视图"和"帮助"。当运行程序后,打开"文件"菜单中的"打开"命令时,应用程序会自动打开相应的"打开"文件通用对话框。之所以有这功能,是因为向导创建的应用程序框架中,自动将"打开"菜单命令与CWinApp的OnFileOpen成员函数相关联。这种关联是通过"消息映射"来实现的,在CTextViewerApp类的实现文件TextViewer.cpp前面有这样的代码,如图2所示。



图2 应用程序类的消息映射


  "消息映射"是MFC中的一个亮点。在Windows操作环境中,无论是系统产生的动作或是用户运行应用程序产生的动作,都称为事件(Events)产生的消息(Message)。例如,用户选择菜单时所产生的消息称为"命令"消息,而鼠标改变窗口状态时所产生的消息是"窗口"消息。只要是消息,都可以通过MFC的"消息机制"来映射。映射的目的是将消息和某个函数相关联,这样一旦该消息产生就会执行相关联的函数。


  图2代码中,BEGIN_MESSAGE_MAP和END_MESSAGE_MAP是MFC开始和结束消息映射宏,ON_COMMAND是专门用来映射像菜单的一些命令消息宏,它有两个参数,第一个参数用来指定命令标识,MFC中每个菜单项都有一个标识值,"打开"菜单的标识ID为ID_FILE_OPEN,第二个参数是用来指定关联的函数。(消息映射以后还会讲到)


  当用户在通用"打开"文件对话框中指定一个文件后,应用程序将调用文档对象的 CDocument::OnOpenDocument虚成员函数。该函数将打开文件,并调用DeleteContents清除文档对象的内容,然后创建一个CArchive(归档类)对象用于数据的读取,接着又自动调用Serialize函数。之后便调用视图对象的CView::OnInitialUpdate虚成员函数。


  在这个过程中,我们可能有很多地方不理解。但我们仔细想一想就会明白许多。假如视图中已有文档数据显示,为了能快速显示和修改这些数据,显然这些数据要存储在专门的内存空间中,CArchive类对象就起到了这个作用。当打开另一个文档时,以前在内存中存储的数据要清除,这就是DeleteContents作用,而且还要使视图能及时更新显示,所以要调用OnInitialUpdate函数。


  上述的Serialize函数是一个很特别的函数,它既可以从中读取文档数据,也可以保存文档数据,称为"序列化"函数。它被添加用户的文档类中,用来根据CArchive内部的一个标志来决定文档数据的流向(读或写),如图3所示。


图3 Serialize函数代码





  文档数据的读操作


  对于上述过程,我们所做的仅仅是在文档类的Serialize函数中添加文档数据读取(加载)和存储的代码。需要说明的是,Serialize函数的参数ar是一个CArchive类的引用对象。CArchive类提供了"<<"和">>"运算符,分别可以向文档对象写入数据或从文档对象中读取数据。它们的含义与C++中的"<<"和">>"运算符相同,只不过CArchive支持更多的数据类型,如:CObject、CString等。除此之外CArchive类还提供ReadString和WriteString成员函数来读写文档中的一行文本。下面的过程用来将文档的文本内容读出并保存到一个字符串集合类对象中。


  (1) 将解决方案管理器窗口切换到"类视图",展开所有的类,右击类名"CTextViewerDoc",从弹出的快捷菜单中选择"添加"->"添加变量",弹出"添加成员变量向导"对话框,在"变量类型"框中输入CStringArray,在"变量名"框中输入m_strContent,如图4所示。单击"完成"按钮。


  CStringArray是"字符串集合类",它封装了CString数组对象的全部操作。类似的还有对BYTE、UINT、WORD和DWORD等类型的数组操作的集合类CByteArray、CUIntArray、CWordArray和CDWordArray。这些集合类都有相似的操作,如Add(添加)、RemoveAll(删除全部元素)、GetAt(获取指定数组下标的元素)等。



图4 添加成员变量


  (2) 在CTextViewerDoc::Serialize函数中添加读取文档文本内容代码,如图5所示。



图5 添加的读取文档文本内容代码


  代码中,ReadString是读取打开的文档的一行文本,当成功读出时,函数返回TRUE,当文本达到文档结尾时,函数返回FALSE。这样,通过while循环可以将文档的文本内容全部读取并保存到m_strContent中。


  (3) 由于另一个文档打开时,需要将m_strContent中的内容清除,所以我们需要跟踪DeleteContents函数。在CTextViewerDoc类的属性窗口,单击"重写"按钮,在列表框中找到DeleteContents函数项,单击右边的空格后再单击右侧的下拉按钮,出现一个下拉列表,如图6所示。


图6 添加DeleteContents函数的重写


  (4) 单击"<添加>DeleteContents",该函数的重写就添加好了。这样,框架在自动执行该函数时就会将自己添加在这个函数中的代码也会被执行。


  (5) 在DeleteContents中添加如图7所示的加框代码。


图7 在DeleteContents函数中添加的代码


  这就是文档文本内容的整个读取过程。需要说明的是,也可以将"m_strContent.RemoveAll();"语句直接添加在图5中的while循环语句之前,从而可以省略(5)和(6)的步骤。





  文档数据的显示方法和技巧


  用户的视图类是负责显示文档数据的,目前常用的显示方法有三个:一是使用CEditView机制来显示,二是在视图的客户区中使用编辑控件,三是直接调用CDC类的文本输出函数绘制所有的文本内容。下面就来介绍。


  1. 使用CEditView机制


  在MFC文档应用程序中,其内部有一个视图指针列表变量m_viewList,由于CEditView支持文档的序列化,因此我们可以使用下列语句来进行:


((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);


  具体过程如下:


  (1) 在InitInstance函数中,改写框架窗口类、文档类和视图类的关联,如图8所示的加框部分。



图8 在文档模板中改变关联的视图类


  CEditView视图类提供了简单的文本编辑功能,如打印、查找并替换、剪贴板的剪切、复制和粘贴等。


  (2) 在CTextViewerDoc::Serialize函数中添加序列化代码,如图9所示的加框部分。



图9 添加的序列化代码


  (3) 运行程序,打开一个文档,看看是不是可以显示出文档的内容?(显示的内容可能会出现乱码,这是Visual C++ .NET中的一个BUG)


  评述:这种方法简单有效,并且能够实现文本的编辑功能,缺点是程序中的CTextViewerView类变得没有用了,并且很难进行更深层次的视图控制。





  2. 使用编辑控件


  "编辑控件"是一个可以让用户从键盘输入和编辑文本的控件,通过它可以输入各种文本、数字或者口令,也可使用它来编辑和修改简单的文本内容。MFC类CEdit封装了编辑控件的全部操作。


  使用编辑控件实现文档数据的显示的思路是,先在视图中创建一个与视图客户区大小相同的编辑控件,然后把文档的文本内容转送到编辑控件中。这里的视图客户区是指除了窗口标题栏、菜单栏、工具栏、状态栏以及边框之外的部分。简单地说,就是默认的背景色为白色的区域。


  实现的步骤如下:


  (1) 为CTextViewerView类添加成员变量CEdit* m_ctrlEdit。这是一个指针变量,用"添加成员变量向导"添加时,要在"变量类型"框加输入"CEdit*"(双引号不输入,注意其中的星号),而在"变量名"中输入m_ctrlEdit。


  (2) 为CTextViewerView类添加OnInitialUpdate函数的重写,并添加如图10所示的代码(加框部分)。



图10 在OnInitialUpdate中添加的代码


  new和delete分别用来为类对象分配和释放内存空间。为了避免m_ctrlEdit内存空间重复分配,我们在new操作前,要先将m_ctrlEdit内存空间释放。


  当框架将文档与视图关联,且视图将要显示时调用OnInitialUpdate函数,因此我们将视图的一些初始化代码添加到这里。


  (3) 在CTextViewerView析构函数中添加m_ctrlEdit内存空间释放的语句: if ( m_ctrlEdit ) delete m_ctrlEdit;


  (4) 由于视图大小改变后,编辑控件的大小也应随之改变,因此我们需要跟踪窗口的WM_SIZE消息,只要窗口大小发生改变后,都会发送这个消息。单击CTextViewerView类属性窗口中的"消息"按钮,添加WM_SIZE消息映射。如图11所示。



图11 添加WM_SIZE的消息映射


  (5) 在消息映射函数CTextViewerView::OnSize中添加如图12所示的代码。



图12 在OnSize中添加的代码


  (6) 运行程序,打开当前文件夹下的ReadMe.txt文件,结果如图13所示。



图13 使用编辑控件的文档数据显示结果


  评述:这种方法虽然也比较简单,且具有文本的编辑功能,但文本显示的格式还很单调,例如它的行间距和字间距无法调整,更主要的是视图的绘制功能无法起作用。





  3. 直接控制文本的输出


  图形和文本的绘制需要用到MFC的CDC类,它是一个设备环境类。所谓设备环境,就好比我们写字用的纸那样,显示时指的是屏幕,打印时指的是打印机。实际上,MFC的CDC类还为一些特殊的设备环境提供相应的派生类。例如,CClientDC是一个窗口客户区的设备环境类。


  CDC为我们提供了四个输出文本的函数:TextOut、ExtTextOut、TabbedTextOut和DrawText,分别用于不同的场合。如果想要绘制的文本需要支持Tab符,那么采用TabbedTextOut函数,可以使绘制出来的文本效果更佳;如果要在一个矩形区域内绘制多行文本,那么采用DrawText函数,会更富于效率;如果文本和图形结合紧密,字符间隔不等,并要求有背景颜色或矩形裁剪特性,那么ExtTextOut函数就将是最好的选择。如果没有什么特殊要求,那使用TextOut函数就显得简练了。在本例中,我们使用TabbedTextOut函数来绘制文本,它的函数原型如下:


CSize TabbedTextOut( int x, int y, const CString& str,
int nTabPositions, LPINT lpnTabStopPositions, int nTabOrigin );


  该函数用当前字体在指定位置 (x,y) 处显示一个由str指定的文本,且根据指定的制表停止位设置相应字符位置。函数成功时返回文本的大小。参数中,nTabPositions表示lpnTabStopPositions数组的大小,lpnTabStopPositions表示多个递增的停止位(逻辑坐标)的数组,nTabOrigin表示制表停止位x方向的起始点(逻辑坐标)。


  具体步骤如下:


  (1) 重新创建一个新的单文档应用程序项目Viewer,在向导的"生成的类"页面中将CViewerView的基类CView改成CScrollView。CScrollView类是一个用来提供自动滚动或缩放功能的视图结构。


  (2) 按文档数据的读操作,在CViewerDoc类中,添加保存文档数据的CStringArray类对象m_strContent,并添加相应的操作代码。


  (3) 文本内容的输出代码一般是添加在视图类的OnDraw函数中,但为了调用的方便,我们这里在视图类中添加一个成员函数DispContent。


  (4) 将解决方案管理器窗口切换到"类视图",展开所有的类,右击类名"CViewerView",从弹出的快捷菜单中选择"添加"->"添加函数",弹出"添加成员函数向导"对话框,在"返回类型"框中输入void,在"函数名"框中输入DispContent,在"参数类型"框中输入CDC*,在"参数名"框中输入pDC,然后单击"添加"按钮,结果如图14所示。单击"完成"按钮。



图14 添加成员函数DispContent


  (5) 在DispContent函数中添加如图15所示的代码。



图15 DispContent函数代码


  SetScrollSizes()是CScrollView类的成员函数,用来设置相应的坐标映射模式和逻辑滚动窗口的大小。所谓"逻辑滚动窗口",是在指定的坐标映射模式下的一个"虚拟窗口"。当虚拟窗口超过视图客户区(可称为"显示窗口")的大小时,视图客户区中就会自动出现滚动条,供用户滚动浏览。若虚拟窗口比显示窗口小,则视图客户区中不会出现滚动条。MM_TEXT是"文本"坐标映射模式。在该模式下,x坐标从左向右递增,y坐标自上而下递增,坐标以像素为单位,这也是MFC的默认坐标模式。


  (6) 在CViewerView::OnDraw函数添加DispContent调用代码,如图16所示的加框部分。OnDraw()是视图类中非常有用的一个函数,当应用程序中的窗口状态或大小发生改变时,系统均会调用此函数重新绘制视图窗口的客户区。因此,我们应该将一些图形绘制添加到此函数中。



图16 DispContent的调用


  (7) 运行程序,打开当前文件夹下的ReadMe.txt文件,结果如图17所示。



图17 文本绘制的结果


  评述:这种方法虽然较复杂一点,但是却能控制每行文本的行距,并可使用CDC类的其他文本和文字处理函数,使得文本表现更具丰富力。另外,由于绘制的代码过程是添加在OnDraw中,因此该方法能使默认的打印和打印预览功能有效。缺点是,显示的速度表现欠佳。


  结束语


  本讲中,通过MFC的文档读过程讨论了文档的读操作及不同的文档数据显示方法和技巧,在下一讲中,我们将通过菜单和工具栏来改变文本显示的字体和颜色,并讨论它们与状态栏这三者之间的相互关系。

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)