Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2716873
  • 博文数量: 416
  • 博客积分: 10220
  • 博客等级: 上将
  • 技术积分: 4193
  • 用 户 组: 普通用户
  • 注册时间: 2006-12-15 09:47
文章分类

全部博文(416)

文章存档

2022年(1)

2021年(1)

2020年(1)

2019年(5)

2018年(7)

2017年(6)

2016年(7)

2015年(11)

2014年(1)

2012年(5)

2011年(7)

2010年(35)

2009年(64)

2008年(48)

2007年(177)

2006年(40)

我的朋友

分类: WINDOWS

2007-01-18 17:09:02

 #define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
 static CObject* PASCAL CreateObject();    文档/视图结构是MFC中最有特色而又有难度的部分,在这当中涉及了应用、文档模板、文档、视图、MDI框架窗口、MDI子窗口等不同的对象,如果不了解这些部分之间如何关联的话,就可能犯错误,也就很难编出有水平的文档/视图程序。比如我在初学VC编程的时候,为应用程序添加了两个文档模板,两个模板公用一个文档类,只是视图不一样,期望当一个模板的文档的视图改变了文档后,调用UpdateAllViews后也能更新另一个文档模板的视图,结果当然是不行的,原因就是对MFC的文档/视图结构没有深入的了解,了解的最好方法就是阅读一下MFC的源代码。下面就是我的笔记:
(一)应用程序对象与文档模板之间的联系:
        首先,在应用程序对象中有一个CDocManager指针类型的共有数据成员m_pDocManager,在CDocManager中维护一个CPtrList类型的链表:m_tempateList,它是一个保护成员。InitInstance函数中调用CWinApp::AddDocTemplate函数,实际上是调用m_pDocManager的AddDocTemplate函数向链表m_templateList添加模板指针。CWinApp提供了GetFirstDocTemplatePosition和GetNextDocTemplate函数实现对m_templateList链表的访问(实际上是调用了CDocManager的相关函数)。
         在文件操作方面CWinApp提供的最常用的功能是文件的新建(OnFileNew)和打开(OnFileOpen),它也是调用CDocManager类的同名函数。对于新建,一般的时候在只有一个文档模板的时候,它新建一个空白的文件;如果有多个文档模板的时候,它会出现一个对话框提示选择文档类型。它的源代码如下:
void CDocManager::OnFileNew()
{
       if (m_templateList.IsEmpty())
       {
                               .......
              return;
       }
                //取第一个文档模板的指针
       CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
       if (m_templateList.GetCount() > 1)
       {
              // 如果多于一个文档模板,出现对话框提示用户去选择
              CNewTypeDlg dlg(&m_templateList);
              int nID = dlg.DoModal();
              if (nID == IDOK)
                     pTemplate = dlg.m_pSelectedTemplate;
             else
                     return;     // none - cancel operation
       }
                ......
                //参数为NULL的时候OpenDocument File会新建一个文件
      pTemplate->OpenDocumentFile(NULL);
}
打开文件:
void CDocManager::OnFileOpen()
{
       // 出现打开文件对话框
       CString newName;
       if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,
         OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
              return; // open cancelled
       AfxGetApp()->OpenDocumentFile(newName);          //实际也是调用文档模板的同名函数
}
(二)文档模板与文档之间的联系:
        从上面看出应用程序对象对文件的新建和打开是依靠文档模板的OpenDocumentFile函数实现的。MFC的模板类是用来联系文档类、视类和框架类的,在它的构造函数就需要这三者的信息:
CDocTemplate ( UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass );
构造函数利用后三个参数为它的三个CruntimeClass*类型的保护成员赋值:
       m_pDocClass = pDocClass;
       m_pFrameClass = pFrameClass;
       m_pViewClass = pViewClass;
    文档模板分为单文档模板和多文档模板两种,这两个模板的实现是不同的,除了上面的三个成员,内部有彼此不相同的但是很重要的成员变量。对于多文档模板:CPtrList m_docList;,单文档模板:CDocument* m_pOnlyDoc;。它们都有一个成员函数AddDocument,分别各自的成员进行赋值操作,而在它们的父类的CDocTemplate中则是为它所添加的文档的m_pDocTemplate变量赋值为模板自己的地址:
void CDocTemplate::AddDocument(CDocument* pDoc)
{
       ASSERT_VALID(pDoc);
       ASSERT(pDoc->m_pDocTemplate == NULL);  
       pDoc->m_pDocTemplate = this;
}
由于单文档模板只能拥有一个文档,所以它只是维护一个指向自己所拥有的模板的指针:m_pOnlyDoc,AddDocument函数就是要为这个成员赋值:
void CSingleDocTemplate::AddDocument(CDocument* pDoc)
{
                ......
       CDocTemplate::AddDocument(pDoc);
       m_pOnlyDoc = pDoc;
}
由于多文档模板可以拥有多个文档,所以它要维护的是包含它所打开的所有文档的指针的链表,所以它的AddDocument的实现为:
void CMultiDocTemplate::AddDocument(CDocument* pDoc)
{
                ......
       CDocTemplate::AddDocument(pDoc);
       m_docList..AddTail(pDoc);
}
   模板通过m_pOnlyDoc(单文档)或记住了自己所拥有的所有的模板的指针,并通过GetFirstDocPosition和GetNextDoc函数可以实现对它所拥有的文档的访问,同时使文档记住了自己所属文档模板的指针,同时文档提供了GetDocTemplate()函数可以取得它所属的模板。
对AddDocument函数的调用主要是发生在另一个成员函数CreateNewDocument里,它的作用是创建一个新的文档:
CDocument* CDocTemplate::CreateNewDocument()
{
       if (m_pDocClass == NULL)
       {
         ……
       }
       CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
    ……
       AddDocument(pDocument);
       return pDocument;
}
CreateNewDocument函数主要利用文档类的运行时指针的函数CreateObject创建一个新文档对象,并利用AddDocument将其指针赋給相关的成员,留做以后使用。
    在应用程序的OnFileNew和OnFileOpen函数都使用了模板的OpenDocumentFile函数,而且在实际编程的时候也大都使用这个函数。在MSDN的文档说这个函数当参数不为NULL的时候打开文件,否则就用上面所说的CreateNewDocument函数创建一个新文档,那么它是如何实现的呢?
CDocument* CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
       BOOL bMakeVisible)
{
       CDocument* pDocument = NULL;
      CFrameWnd* pFrame = NULL;
       BOOL bCreated = FALSE;      // => doc and frame created
       BOOL bWasModified = FALSE;
    //如果已经有打开的文档,就会询问否保存文件
       if (m_pOnlyDoc != NULL)
       {
              pDocument = m_pOnlyDoc;
              if (!pDocument->SaveModified())
                     return NULL;     
              pFrame = (CFrameWnd*)AfxGetMainWnd();
                               ......
       }
    //创建新文件
       else
       {
              pDocument = CreateNewDocument();
              ASSERT(pFrame == NULL);    
              bCreated = TRUE;
       }
                ......
    //如果第一次创建文档则也要创建框架窗口。
       if (pFrame == NULL)
       {
              ASSERT(bCreated);
              // create frame - set as main document frame
              BOOL bAutoDelete = pDocument->m_bAutoDelete;
              pDocument->m_bAutoDelete = FALSE;
              pFrame = CreateNewFrame(pDocument, NULL);
              pDocument->m_bAutoDelete = bAutoDelete;
       }
       if (lpszPathName == NULL)
       {
              // 为新文档设置默认标题
              SetDefaultTitle(pDocument);
        ……
      //一般的时候重载OnNewDocument初始化一些数据,如果返回FALSE,表示初始化失//败,销毁窗口。
              if (!pDocument->OnNewDocument())
              {
                                                ......
                     if (bCreated)
                            pFrame->DestroyWindow();    // will destroy document
                     return NULL;
              }
       }
       else
       {
              CWaitCursor wait;
 
              // open an existing document
              bWasModified = pDocument->IsModified();
              pDocument->SetModifiedFlag(FALSE);
              //OnOpenDocument函数重新初始化文档对象
              if (!pDocument->OnOpenDocument(lpszPathName))
              {
                     if (bCreated)
                     {
                //新建文档的情况
                            pFrame->DestroyWindow();   
                     }
                     else if (!pDocument->IsModified())
                     {
                            // 文档没有被修改,恢复原来文档的修改标志
                            pDocument->SetModifiedFlag(bWasModified);
                     }
                     else
                     {
                            // 修改了原始的文档
                            SetDefaultTitle(pDocument);
 
                            if (!pDocument->OnNewDocument())
                            {
                                   TRACE0("Error: OnNewDocument failed after trying to open a document - trying to continue.\n");
                            }
                     }
                     return NULL;        // open failed
              }
              pDocument->SetPathName(lpszPathName);
       }
 
       CWinThread* pThread = AfxGetThread();
       if (bCreated && pThread->m_pMainWnd == NULL)
       {
              pThread->m_pMainWnd = pFrame;
       }
       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
 
       return pDocument;
}
以下是多文档模板的OpenDocumentFile的实现
CDocument* CMultiDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName,
       BOOL bMakeVisible)
{
       //新建一个文档对象
       CDocument* pDocument = CreateNewDocument();
……
 
       BOOL bAutoDelete = pDocument->m_bAutoDelete;
       pDocument->m_bAutoDelete = FALSE;  
       CFrameWnd* pFrame = CreateNewFrame(pDocument, NULL);
       pDocument->m_bAutoDelete = bAutoDelete;
……
 
       if (lpszPathName == NULL)
    //当是新建的时候
       {
              SetDefaultTitle(pDocument);
 
              // avoid creating temporary compound file when starting up invisible
              if (!bMakeVisible)
                     pDocument->m_bEmbedded = TRUE;
 
              if (!pDocument->OnNewDocument())
              {
                     pFrame->DestroyWindow();
                     return NULL;
              }
 
              m_nUntitledCount++;
       }
       else
       {
              // 打开一个已经存在的文件
              CWaitCursor wait;
              if (!pDocument->OnOpenDocument(lpszPathName))
              {
                     // user has be alerted to what failed in OnOpenDocument
                     TRACE0("CDocument::OnOpenDocument returned FALSE.\n");
                     pFrame->DestroyWindow();
                     return NULL;
              }
              pDocument->SetPathName(lpszPathName);
       }
 
       InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
       return pDocument;
}
    从上面看出模板类的OpenDocumentFile函数里,利用CreateNewDocument对象使文档对象与模板对象建立了联系,利用了CreateNewFrame函数使框架窗口与文档、视图、模板发生了联系:
 
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)
{
       if (pDoc != NULL)
              ASSERT_VALID(pDoc);
 
       ASSERT(m_nIDResource != 0); // 必须有资源ID
       CCreateContext context;
       context.m_pCurrentFrame = pOther;
       context.m_pCurrentDoc = pDoc;
       context.m_pNewViewClass = m_pViewClass;
       context.m_pNewDocTemplate = this;
 
       if (m_pFrameClass == NULL)
       {
           ……
       }
       CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
       if (pFrame == NULL)
       {
        ……
              return NULL;
       }
       ASSERT_KINDOF(CFrameWnd, pFrame);
 
       if (context.m_pNewViewClass == NULL)
              TRACE0("Warning: creating frame with no default view.\n");
 
       if (!pFrame->LoadFrame(m_nIDResource,
                     WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,   // default frame styles
                     NULL, &context))
       {
……
              return NULL;
       }
       return pFrame;
}
从上面的函数 可以看出文档、视图、框架窗等都是通过RUNTIME_CLASS宏得到的CRuntimeClass指针调用CreateObject函数得到的. RUNTIME_CLASS的定义为:
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
在每个运行使用这个宏的类的声明里都有这样的声明:
protected:
DECLARE_DYNCREATE(CUIDoc)
它的定义是:
#define DECLARE_DYNCREATE(class_name) \
 DECLARE_DYNAMIC(class_name) \
 static CObject* PASCAL CreateObject();
#ifdef _AFXDLL
#define DECLARE_DYNAMIC(class_name) \
protected: \
 static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
 static const AFX_DATA CRuntimeClass class##class_name; \
 virtual CRuntimeClass* GetRuntimeClass() const;
#else
#define DECLARE_DYNAMIC(class_name) \
public: \
 static const AFX_DATA CRuntimeClass class##class_name; \
 virtual CRuntimeClass* GetRuntimeClass() const;
#endif
上面的宏声明了CreateObject函数,在IMPLEMENT_DYNCREATE宏里实现了它:
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
 CObject* PASCAL class_name::CreateObject() \
  { return new class_name; } \
 IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
  class_name::CreateObject)
CRuntimeClass就是调用了每个类相应的静态函数CreateObject创建了对象。查看向导 为 我们 生成的代码会发现文档、视图等对象它们的构造函数都是保护类型,我们是不能直接创建它们的,MFC而是通过上面的途径间接创建的。
总结:在模板里使用自己的数据结构维护着自己拥有的文档对象,并提供了GetFirstDocPosition和GetNextDoc函数实现对这些文档的对象的访问。所以,在一个拥有多个文档模板的应用程序中,即使每个模板使用了相同类型的文档类,每个新建或打开的文档在这些文档模板之间也不是共享的。
(三)文档与视图之间的联系
在视图类有一个保护数据成员:CDocument* m_pDocument;,这个文档指针指向视图对象所属的文档,视图里常用的函数GetDocument()就是返回的这个指针;在文档类有一个保护数据成员:CDocument* m_viewList;,它保存的是所有正在显示该文档的视图的指针,通过CDocument的成员函数GetFirstViewPosition和GetNextView函数可以实现对这些视图的访问。
在视图被创建的时候,在OnCreate函数里视图和文档发生了关联:
int CView::OnCreate(LPCREATESTRUCT lpcs)
{
       if (CWnd::OnCreate(lpcs) == -1)
              return -1;
       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
 
       if (pContext != NULL && pContext->m_pCurrentDoc != NULL)
       {
              pContext->m_pCurrentDoc->AddView(this);
              ASSERT(m_pDocument != NULL);
       }
       else
       {
              TRACE0("Warning: Creating a pane with no CDocument.\n");
       }
 
       return 0;  
}
这个关联是通过文档类的AddView函数实现的:
void CDocument::AddView(CView* pView)
{
    ……
       m_viewList.AddTail(pView);
       pView->m_pDocument = this;
 
       OnChangedViewList();  
}
在这个函数里,首先文档对象先把所添加的视图指针加到自己的视图链表里,然后指向自己的指针赋給了所添加的视图的m_pDocument成员。
众所周知,文档与视图进行通信的方式先调用文档的UpdateAllViews函数,从而调用视图的OnUpdate函数:
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
       // walk through all views
{
    //视图链表不能为空且发送者不能为空
       ASSERT(pSender == NULL || !m_viewList.IsEmpty());
       POSITION pos = GetFirstViewPosition();
       while (pos != NULL)
       {
              CView* pView = GetNextView(pos);
              ASSERT_VALID(pView);
      //不调用发送者的OnUpdate函数
              if (pView != pSender)
                     pView->OnUpdate(pSender, lHint, pHint);
       }
}
在视图的OnUpdate函数里默认的实现仅是通知视图进行重画:
Invalidate(TRUE);
我们一般重载这个更新视图的某些数据或进行其他操作,比如更新视图滚动条的滚动范围。
(四)框架窗口与文档、视图之间的联系
 
在框架窗口被创建的时候,创建了视图,相关的函数如下:
int CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)
{
       CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
       return OnCreateHelper(lpcs, pContext);
}
int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
       if (CWnd::OnCreate(lpcs) == -1)
              return -1;
 
       // create special children first
       if (!OnCreateClient(lpcs, pContext))
       {
              TRACE0("Failed to create client pane/view for frame.\n");
              return -1;
       }
 
       // post message for initial message string
       PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);
 
       // make sure the child windows have been properly sized
       RecalcLayout();
 
       return 0;   // create ok
}
BOOL CFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)
{
       // default create client will create a view if asked for it
       if (pContext != NULL && pContext->m_pNewViewClass != NULL)
       {
              if (CreateView(pContext, AFX_IDW_PANE_FIRST) == NULL)
                     return FALSE;
       }
       return TRUE;
}
CWnd* CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)
{
 
       CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
       if (pView == NULL)
       {
              return NULL;
       }
       ASSERT_KINDOF(CWnd, pView);
 
       if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
              CRect(0,0,0,0), this, nID, pContext))
       {
              return NULL;        // can't continue without a view
       }
 
       if (afxData.bWin4 && (pView->GetExStyle() & WS_EX_CLIENTEDGE))
       {
              ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);
       }
       return pView;
}
在文档模板的OpenDocumentFile函数发生了如下的调用:
InitialUpdateFrame(pFrame, pDocument, bMakeVisible);
文档模板的这个函数的实现为:
pFrame->InitialUpdateFrame(pDoc, bMakeVisible);
实际是调用了框架窗口的同名函数:
void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)
{
       CView* pView = NULL;
       if (GetActiveView() == NULL)
       {
              //取主视图
              CWnd* pWnd = GetDescendantWindow(AFX_IDW_PANE_FIRST, TRUE);
              if (pWnd != NULL && pWnd->IsKindOf(RUNTIME_CLASS(CView)))
              {
        //主视图存在且合法,把当前的主视图设置为活动视图
                     pView = (CView*)pWnd;
                     SetActiveView(pView, FALSE);
              }
       }
 
       if (bMakeVisible)
       {
              SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
 
              if (pView != NULL)
                     pView->OnActivateFrame(WA_INACTIVE, this);
        ……
              ActivateFrame(nCmdShow);
              if (pView != NULL)
                     pView->OnActivateView(TRUE, pView, pView);
       }
 
       // update frame counts and frame title (may already have been visible)
       if (pDoc != NULL)
              pDoc->UpdateFrameCounts();
       OnUpdateFrameTitle(TRUE);
}
上面的函数中对视图的操作主要是用SetActiveView设置了活动视图,并且调用了视图的OnActivateFrame函数。在CFrameWnd类中维护着一个保护成员:CView* m_pViewActive;,SetAcitveView函数主要就是对它进行操作:
void CFrameWnd::SetActiveView(CView* pViewNew, BOOL bNotify)
{
       CView* pViewOld = m_pViewActive;
       if (pViewNew == pViewOld)
              return;     // do not re-activate if SetActiveView called more than once
 
       m_pViewActive = NULL;   // no active for the following processing
 
       // deactivate the old one
       if (pViewOld != NULL)
              pViewOld->OnActivateView(FALSE, pViewNew, pViewOld);
 
       if (m_pViewActive != NULL)
              return;     // already set
       m_pViewActive = pViewNew;
 
       // activate
       if (pViewNew != NULL && bNotify)
              pViewNew->OnActivateView(TRUE, pViewNew, pViewOld);
}
CFrameWnd还有另一个函数返回这个成员:
CView* CFrameWnd::GetActiveView() const
{
       ASSERT(m_pViewActive == NULL ||
              m_pViewActive->IsKindOf(RUNTIME_CLASS(CView)));
       return m_pViewActive;
}
CframeWnd还有一个函数能取得当前活动的文档,它是通过活动视图间接得到的:
CDocument* CFrameWnd::GetActiveDocument()
{
       ASSERT_VALID(this);
       CView* pView = GetActiveView();
       if (pView != NULL)
              return pView->GetDocument();
       return NULL;
}
(五)MDI主窗口和子窗口之间的关联:
在MDI子窗口创建的时候,指定了它与MDI之间的关系:
BOOL CMDIChildWnd::Create(LPCTSTR lpszClassName,
       LPCTSTR lpszWindowName, DWORD dwStyle,
       const RECT& rect, CMDIFrameWnd* pParentWnd,
       CCreateContext* pContext)
{
       if (pParentWnd == NULL)
       {
              CWnd* pMainWnd = AfxGetThread()->m_pMainWnd;
              ASSERT(pMainWnd != NULL);
              ASSERT_KINDOF(CMDIFrameWnd, pMainWnd);
              pParentWnd = (CMDIFrameWnd*)pMainWnd;
       }
    ……
       pParentWnd->RecalcLayout();
 
       CREATESTRUCT cs;
……
//指定了所属的MDI子窗口
       cs.hwndParent = pParentWnd->m_hWnd;
    ……
       cs.lpCreateParams = (LPVOID)pContext;
       if (!PreCreateWindow(cs))
      {
              PostNcDestroy();
              return FALSE;
       }
       MDICREATESTRUCT mcs;
    ……
       mcs.style = cs.style & ~(WS_MAXIMIZE | WS_VISIBLE);
       mcs.lParam = (LONG)cs.lpCreateParams;
       AfxHookWindowCreate(this);
    //发送WM_MDICREATE消息,创建了MDI子窗口
       HWND hWnd = (HWND)::SendMessage(pParentWnd->m_hWndMDIClient,
              WM_MDICREATE, 0, (LPARAM)&mcs);
       if (!AfxUnhookWindowCreate())
              PostNcDestroy();        // cleanup if MDICREATE fails too soon
   ……
       return TRUE;
}
当MDI子窗口创建了以后,MDI主窗口就可以用自己的函数实现对子窗口的管理,例如取得当前的活动子窗口是通过发送WM_MDIGETACTIVE消息实现的
 
阅读(1333) | 评论(0) | 转发(0) |
0

上一篇:软件工程

下一篇:Visual C++ 入门精解(1)

给主人留下些什么吧!~~