文档视图(3)

(三)、了解CwinApp::OnFileNew、CwinApp::OnFileOpen和Window/New的程序流程。

(1)、CwinApp::OnFileNew和CwinApp::OnFileOpen函数的简单流程。

      在CWinApp::OnFile/new 或CwinApp::OnFileOpen函数中,核心操作是CDocTemplate::OpenDocument函数。其函数原型为:

virtual CDocument* CDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
      BOOL bMakeVisible =TRUE ) = 0;

图(4)中星号标注之后即是该函数的流程,简要介绍如下:

(1)、CDocTemplate::CreateNewDocument函数创建一个新文档,其类型与文档模板相关,并通过函数CDocTemplate::AddDocument加入该文档模板的文档指针列表中。此时,文档类的构造函数被执行,程序可以在此进行文档的初始化。

(2)、函数CDocTemplate::CreateNewFrame调用MDI子窗口类(CMDIChildWnd)的构造函数,生成MDI子窗口对象。接着调用CMDIChildWnd::PreCreateWindow。然后,生成一个CCreateContext对象,(CcreateContext是MFC框架所使用的一种结构,它将构成文档和视的组件联系起来。后文将详细介绍之。)并将该对象值传给CMDIChildWnd::OnCreateClient函数。MFC调用此函数,用CCreateContext对象提供的信息创建一个或多个CView对象。此时,各视的构造函数被依次调用。

(3)、接着,判断lpszPathName是否为空。分为两种情况:

(a)、若为空,则表明要创建一个新文档:调用SetDefaultTitle函数装载文档的缺省标题,并显示在文档的标题栏中;然后执行CDocument::OnNewDocument。该函数调用DeleteContents以保证文档为空,然后置新文档为清洁。可以重载该函数。

(b)、否则,表明要打开一个已存在的文档:调用CDocument::OnOpenDocument打开指定的文件;执行DeleteContext,保证文档为空;调用CObject::Serialize读入该文件的内容。(程序员可在此进行文件的读入操作。当然,也可以在CDocument::OnOpenDocument中读入文件)。然后置文档为清洁;最后,调用CDocTemplate::SetPathName,并把文件名加入到最近文件列表中。

(4)、调用CDocTemplate::InitialUpdateFrame函数,使框架窗口中的各个视收到OnInitialUpdate调用。框架窗口的主视(子窗ID等于AFX_IDW_PANE_FIRST的视)被激活。程序员可以在此对视对象进行初始化。

(2)、Window/New命令的程序流程

      当主框架窗口上有子窗口时,选择Window/New命令可以生成该活动子窗口的影象。它们有相同的文档模板、相同的文档。其流程如下:
 
      执行Window/New的过程与File/New的过程差不多。所不同的是,File/New须要创建一个新文档,而Window/New则是获得已存在的MDI子窗口的文档。因此以前存在的视和New以后生成的视均为该文档的视,都是该文档的内容的显示。当调用CDocument::UpdateAllViews函数时,它们(视)的OnUpdate函数都将被激活。此时,在该文档的视指针列表中,将有多于一个的视(具体数目视Window/New执行的次数而定)。读者可以利用(图3)中的代码跟踪程序结果。
 
(四)、几种情况的讨论

上面,笔者就MFC中文档/视的关系进行了分析,下面,笔者将结合具体情况进行讨论:

(1)、如何根据自己的要求来选择文档模板,及相应的视和文档。

      在通常的MDI应用程序中,只有一个文档模板,程序员只能打开一种类型的文档。因此,程序员只要调用File/New或者File/Open创建或者打开文档即可,至于文档、视和框架窗口之间的关系,由文档模板在幕后控制,不须要对文档模板进行操作。但是,如果应用程序需要处理多种类型的文档,并且何时打开何种文档均需程序员手工控制,此时,程序员必须对文档模板进行编程。

      例如,笔者需要处理AVI和BMP两种文件类型。AVI和BMP的数据存放格式不同,不能用同一的数据结构来描述,因此,把它们的数据都存入一个文档是不合适的。同时,由于AVI是图象序列,BMP仅是一幅图象,它们的显示是肯定不一样的,即它门的视不同。基于此,笔者决定分别建立两套文档模板,两套框架窗口,两套文档和两套视,分别用于AVI和BMP的数据存放和显示。程序可以根据用户选择的文件名来分别处理AVI和BMP。具体步骤如下:

(Step 1)、在应用程序类(CWinApp)的派生类中增加文档模板成员变量,以便对文档模板进行操作。

class C3dlcsApp : public CWinApp
{
      ......
public:
      CMultiDocTemplate * m_pAVIDocTemplate;
      CMultiDocTemplate * m_pBMPDocTemplate;
}

(Step 2)、在主框架中增加菜单响应:

void CMainFrame::OnFileOpen()
{
      CFileDialog my(true);
      if(my.DoModal()==IDOK)
      {
            CString FileName = my.GetPathName();
            CString FileExt = my.GetFileExt();
            if((FileExt == "AVI") || (FileExt == "avi"))
            {
                  CMyApp * pMyApp = (CMyApp *)AfxGetApp();
                  CMultiDocTemplate*pAVIDocTemplate=pMyApp->m_pAVIDocTemplate;
                  pAVIDocTemplate->OpenDocumentFile(FileName);
            }

            else if((FileExt == "BMP") || (FileExt == "bmp"))
            {
                  CMyApp * p3dlcsApp = (CMyApp *)AfxGetApp();
                  CMultiDocTemplate* pDATDocTemplate=pMyApp->m_pBMPDocTemplate;
                  pDATDocTemplate->OpenDocumentFile(FileName);
            }
            else
            {
                  AfxMessageBox("Yor select a file not supported!");
                  return;
            }
      }
}

      笔者把用户输入文件名的后缀作为分支条件,如果是AVI文件,则先获得关于AVI文件的文档模板,然后调用CDocTemplate::OpenUpdateFrame (lpszFileName)函数打开此文档。正如前面所分析,此函数将依次生成新文档,新框架,在CMDIChildWnd::OnCreateClient中创建视,最后向框架中所有的视发送初始化消息,使其显示在屏幕上。如果是BMP文件,操作类似。

      当然,程序员也可以在程序的任何位置实现此操作:通过全局函数AfxGetApp 获得应用程序对象指针,从而获得相应的文档模板指针。

      由于由AppWizard生成的应用程序会缺省调用CWinApp::OnFileNew,所以当程序开始执行时,会在主框架上显示一个新的空窗口。如果想去掉这个空窗口,只须重载CWinApp::OnFileNew函数,不许要任何代码,即可。
 
(2)、切分窗口与文档/视结构

      一个文档可以有多个视,切分窗口即是表示多视的一种方法。 切分窗口是通过类CSplitterWnd来表示的,对Window来说,CSplitterWnd对象是一个真正的窗口,它完全占据了框架窗口的客户区域,而视窗口则占据了切分窗口的窗片区域。切分窗口并不参与命令传递机制,(窗片中)活动的视窗从逻辑上来看直接被连到了它的框架窗口中。

      切分窗口可以分为动态和静态两种。前者较简单,本文仅讨论后者。创建切分窗口的步骤如下:

(Step 1)、在自己的框架窗口中声明成员变量,用以对切分窗口进行操作。

class CMyFrame : public CMDIChildWnd
{
      ......
      CSplitterWnd m_Splitter;
      CSplitterWnd m_Splitter2;
}

(Step 2)、重载CMDIChildWnd::OnCreateClient函数,创建切分窗口。

BOOL CMyFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
      BOOL btn = m_Splitter.CreateStatic(this,1,2);
      btn |= m_Splitter.CreateView(0,0, RUNTIME_CLASS(CAVIDispView), CSize(100,100), pContext);

      m_Splitter2.CreateStatic(&m_Splitter, 2, 1,
            WS_CHILD | WS_VISIBLE | WS_BORDER,
            m_Splitter.IdFromRowCol(0, 1));

      btn |= m_Splitter2.CreateView(0, 0, RUNTIME_CLASS(CBMPView), CSize(100,100), pContext);
      btn |= m_Splitter2.CreateView(1, 0, RUNTIME_CLASS(CAVIView), CSize(100,100), pContext);

      return btn;
      //return CMDIChildWnd::OnCreateClient(lpcs, pContext);
}

CFrameWnd::OnCreateClient函数原形为:

virtual BOOL OnCreateClient(LPCREATESTRUCT lpcs, CcreateContext * pContext);

      缺省的CMDIChildWnd::OnCreateClient函数根据pContext参数提供的信息,调用CFrameWnd::CreateView函数创建一个视。可以重载该函数,加载CCreateContext对象中传递的值,或改变框架窗口主客户区中控制的创建方式。在上面的程序中,笔者 创建了3个切分窗口。比如打开了一个名为“a.avi”的文档,此时该文档将有3个视,一个框架窗口。如果执行了Window/New操作,则此时有一个文档,6个视和2个框架窗口。若该文档调用CDocument::UpdateAllViews函数,则这6个视的CView::OnUpdate函数都会被激发。

(3)、关于CCreateContext的讨论。

      CCreateContext是MFC框架所使用的一种结构,它将构成文档/视的组件联系起来。这个结构包括指向文档的指针,框架窗口,视以及文档模板,它还包含一个指向CRuntimeClass的指针,以指明所创建的视的类型。其数据成员如下:

m_pNewViewClass:指向创建上下文的视的CRuntimeClass的指针。
m_pCurrentDoc:指向文档对象的指针,以和新视联系起来。
m_pNewDocTemplate:指向与框架窗口的创建相联系文档模板的指针。
m_pLastView:指向已存在的视,它是新产生的视的模型。
m_pCurrentFrame:指向已存在的框架窗口,它是新产生的框架窗口的模型。

      程序员可以通过改变CCreateContext对象的值,来创建更加灵活的视。由于过程较复杂,笔者不再赘许叙,读者可参阅相关的Visual C++ Help文档。

(五)、结束语
      Visual C++ 6.0的文档/视结构代表了一种新的程序设计方式,其核心是文档与视的分离,即数据存放与显示(操作)的分离。在MFC类库中,各个对象之间的关系很复杂,但,只要深入了解后,会发现它们之间是相互联系的,可以相互存取的。如果大家想设计出灵活、健壮的应用程序,就必须深入了解MFC。跟踪原代码就是一个较好的方法。文档/视的关系的确非常复杂,如果能知道每个函数是在哪调用的,执行了何种操作,就能游人刃有余,写出优美的应用程序。 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值