【编者按】网学网ASP.net频道为大家收集整理了“Visual C++.NET编程讲座之七“提供大家参考,希望对大家有所帮助!
MFC"一档多视"模式
我们知道,MFC应用程序用一种编程模式使程序中数据与它的显示形式和用户交互分离开来,这种模式就是"文档/视图结构"。在单文档应用程序结构中,一个文档对应于一个视图。但有时一个文档可能需要多个视图以改变文档数据的显示方式,称为"一档多视",MFC对于这种"一档多视"提供下列三个模式:
第一种模式是用在多文档应用程序中,用同一个视图类创建多个视图对象,并在各自的窗口中显示。例如,当我们选择"窗口"菜单的"新建窗口"命令,程序就会打开一个新的窗口显示出相同的文档内容。在界面上表现为,一个框架窗口中有多个文档窗口,如图1(a)所示。
第二种模式是动态切分窗口方式,即在同一个文档窗口中创建多个视图,用同一方式来显示相同的文档内容。如图1(b)所示。
第三种模式是静态切分窗口方式,即在同一个文档窗口中创建多个视图,但每个视图可用不同的方式来显示文档内容。如图1(c)所示。
(a)
(b)
(c)
图1 "一档多视"的三种模式
但在本讲中所讨论的"一档多视"是指在单文档应用程序中具体多个视图的特性。文档窗口每次只有一个视图显示,但可以通过菜单等命令在多个视图中进行切换。我们的目是将文档内容在"普通文本"和"HTML浏览"视图之间进行切换,如图2(a)和2(b)所示。
(a)
(b)
图2 视图切换的结果
添加"HTML浏览"视图
为了使我们的这个文本浏览器功能更加强大,我们添加一个"HTML浏览"视图用来正确显示出扩展名为.htm或.html等的Web页效果。
1. 添加视图类CWebView
我们先来添加一个视图类CWebView,具体步骤如下:
(1) 启动Visual Studio .NET,打开上一讲的单文档应用程序项目Viewer。
(2) 打开"项目"菜单,单击"添加类",弹出"添加资源"对话框,展开左边的所有"类别",单击"MFC",在右侧模板中选中"MFC类",如图3所示。
图3 "添加类"对话框
(3) 单击"打开"按钮,弹出"MFC类向导"对话框。在对话框中输入"类名"CWebView,然后将基类选择为CHtmlView,CHtmlView类封装了URL资源的浏览和链接等功能。其它为默认值,结果如图4所示。
图4 使用"MFC类向导"
(4) 单击"完成"按钮。
2. 添加代码并测试CWebView
(1) 打开CWebView类的接口文件WebView.h,在最前面加上CHtmlView类的包含文件" "。
(2) 为CWebView类添加OnInitialUpdate函数的重载,并添加如图5加框部分的代码。
图5 在OnInitialUpdate中添加的代码
(3) 打开CViewerApp::InitInstance函数,将CSingleDocTemplate中的第4个参数的视图类由原来的CViewerView改为CWebView。
(4) 在CViewerApp类的实现文件前面加上" "。
(5) 运行程序。图6是显示某个文件的结果。
图6 CWebView类的显示效果
(6) 再把CViewerApp::InitInstance函数中的CSingleDocTemplate参数CWebView改回到CViewerView。
最一般的切换方法
对于单文档框架窗口中的多个视图的切换,我们先看看大多数Visual C++程序员普遍采用的一种方法。具体过程如下:
(1) 打开Viewer项目的Menu资源IDR_MAINFRAME,在"视图"菜单中添加三个菜单项,一个是水平线(分隔符),一个是"普通文本(&G)",ID号设为ID_VIEW_TEXT,最后一个是"HTML浏览(&H)",ID号设为ID_VIEW_HTML。结果如图7所示。
图7 在"视图"中添加的菜单项
(2) 视图切换的代码应该添加在CMainFrame类中。我们先在CMainFrame类添加一个切换函数SwitchToView1,代码如图8所示。
图8 SwitchToView1函数代码
函数SwitchToView1用了一些底层的MFC调用,我们来看看其代码执行过程。
如果主程序框架窗口一开始就激活CViewerView,即pOldView指向CViewerView,则当在程序中调用SwitchToView1(1)时,即参数nView为1,该函数中的第一行代码用来获得当前活动的视图,第二行代码是通过GetDlgItem获得标识为1的窗口指针,由于视图也是一个窗口,所以可以直接进行指针类型的强制转换。第一次调用时,标识为1的窗口指针是不存在的,因而这个视图指针就是NULL,从而执行if语句中的代码,该代码是通过CCreateContext来创建一个新的视图对象指针。
CCreateContext是用来将框架窗口、文档和视图关联起来,在主程序创建框架窗口以及文档相关联的视图时,会自动使用CCreateContext类结构;该结构包含了指向文档、框架窗口和文档模板的指针,以及一个CRuntimeClass指针。该结构还包含一些成员变量指针,如m_pCurrentDoc是用来指定和新创建的视图相关联的文档指针。
接下来的代码比较好理解,pOldView->SetDlgCtrID以及SetWindowLong均是用来为新旧视图窗口重新指定一个标识。需要说明的是,在MFC底层机制中,应用程序框架窗口中默认的文档窗口(视图)标识是使用预定义的AFX_IDW_PANE_FIRST。最后一行的RecalcLayout用来重新布置框架窗口中的所有对象,包括文档窗口和视图。
当再一次调用SwitchToView1(1)时,由于上次SwitchToView1调用将CViewerView类指针与标识1建立联系,因而SwitchToView1中的if语句中的代码就不会被执行,从而避免了CViewerView类视图对象再一次被创建。这也是为什么要使用SetDlgCtrID语句的原因。
(3) 在MainFrm.cpp文件的前面添加一些文件包含语句,如图9所示的加框部分。
图9 添加的包含文件语句
(4) 在MainFrm.h文件的前面添加下列代码,如图10所示的加框部分。
图10 在MainFrm.h中添加的语句
不少人对上述语句不理解:既然使用了包含文件,为什么还要在class CMainFrame前添加"class CViewerView;"等代码?如果用包含文件代替它,行不行?
很多Visual C++书籍对这些问题避而不谈,但实际上这是一个重要的问题。如果不能理解上述代码,我们很可能为无法通过编译而大伤脑筋。这些问题的出现是基于这样的一些事实:在我们用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。为了解决这个难题,Visual C++使用#pragma once来通知编译器在生成时只包含(打开)一次,也就是说,在第一次#include之后,编译器重新生成时不会再对这些包含文件进行包含(打开)和读取,因此我们看到在用向导创建的所有类的头文件中有#pragma once语句就不会觉得奇怪了。然而正是由于这个语句而造成了在第二次#include后编译器无法正确识别所引用的类。因此,我们在相互包含时还需要加入类似class CViewerView这样的语句来通知编译器这个类是一个实际的调用。
(5) 重新生成解决方案后运行程序,看看有没有出现编译错误,此时提示出SwitchToView1函数中,CViewerView和CWebView类无法构造,因为它们的构造函数是protected。
(6) 分别在ViewerView.h和WebView.h文件中,将构造函数CViewerView()和CWebView()前面的访问方式改成public。再运行程序。
(7) 为CMainFrame类添加一个int类型的成员变量m_nViewID,并将其初值设为1。
(8) 为CMainFrame类添加菜单项ID_VIEW_TEXT的COMMAND和UPDATE_COMMAND_UI的事件映射,并在映射函数添加如图11所示的代码。
图11 ID_VIEW_TEXT的映射函数代码
(9) 为CMainFrame类添加菜单项ID_VIEW_HTML的COMMAND和UPDATE_COMMAND_UI的事件映射,并在映射函数添加如图12所示的代码。
图12 ID_VIEW_HTML的映射函数代码
(10) 运行程序,结果如前面图2所示。
使用CDocument类的AddView和RemoveView来切换
在CDocument类中,AddView和RemoveView可以说是专门用于单文档视图的切换的,具体实现过程如下:
(1) 在CMainFrame类添加两个变量,一个是CViewerView类指针对象m_pMainView,另一个是CWebView类指针对象m_pWebView。需要说明的是,用"添加成员变量向导"添加指针对象m_pMainView时,指定的类型名应是CViewerView*,注意后面的星号。m_pWebView添加时也类似。
(2) 为CWebView类添加一个CString类成员变量strFileName。
(3) 将CWebView::OnInitialUpdate中的语句" "删除。
(4) 在CMainFrame类添加成员函数SwitchToView2,用于切换视图,其代码如图13所示。
图13 SwitchToView2函数代码
(5) 将CMainFrame::OnViewText和OnViewHtml中的SwitchToView1函数调用改为SwitchToView2,其余不变。
(6) 运行程序,结果如前图2所示。
两种切换方法的比较
SwitchToView1切换方法实质上是创建两个具有与文档相关联的视图,即视图创建时指定CCreateContext结构,具有很直接的"一档多视"的关系。而SwitchToView2方法是通过CDocument类的AddView和RemoveView来改变一个文档与多个视图的关联。
由于SwitchToView1中创建的视图带有文档关联,因此可以直接在视图类中通过GetDocument()函数来获取相关联的文档指针,从而可以访问文档中的数据。而SwitchToView2中创建的视图本身不带文档关联,因此无法直接访问文档中的数据。
由于SwitchToView2中需要在函数外指定视图类指针变量,因此在各个视图中可以通过AfxGeMainWnd()获取CMainFrame类指针,从而可以直接该问到CMainFrame类中定义的public视图类指针变量,这样便可在视图类之间直接访问。而SwitchToView1中的视图类指针变量是在函数内定义的,因此无法在各个视图类中进行视图之间的访问,并且在本例中CViewerView类对象被创建了两次。SwitchToView2中,CViewerView类对象只创建一次。
除了上述两种方法外,还有一种方法,那就是使用静态切分机制来进行。由于它涉及过多的底层方法,对初学者而言,相对较难,因此这里不再给出。
结束语
在本讲中,我们重点讨论了视图类的添加、单文档的视图切换方法和技巧。对于初学者来说,学习并掌握这些技巧能对MFC的文档/视图机制有一个较为深入的了解。当然,MFC文档/视图机制本身是非常复杂的,在以后的学习中应慢慢地体会和理解。在最后一讲中,我们将重点应用程序的安装和部署。