- 浏览: 73756 次
- 性别:
- 来自: 上海
文章分类
最新评论
文档/视图结构是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;
}
总结:在模板里使用自己的数据结构维护着自己拥有的文档对象,并提供了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)
<p cla发表评论
-
自己写的一段customerDraw
2010-08-24 16:20 1897void CNewReportPage::OnNMCustom ... -
帖BitMap
2010-08-24 09:42 793void CEx_14View::O ... -
clistctrl +号按钮绘制
2010-08-11 17:18 1588网上有关clistctrl举不胜举,包括按钮控件的绘 ... -
listctrl总结2
2010-08-11 17:11 99420. listctrl的subitem添加图标 ... -
VC中使用ListCtrl经验总结(1)
2010-08-11 17:07 1334ListCtrl在工作中,常常用到,也常常看到大家发帖问怎么用 ... -
LV_COLUMN结构体
2010-08-10 14:44 2719typedef struct _LV_COLUMN {UINT ... -
加载位图BITMAP
2010-08-10 14:27 1458m_Bitmap.LoadBitmap(IDB_BITMAP1 ... -
关于控件重绘函数/消息 OnPaint,OnDraw,OnDrawItem,DrawItem的区别
2010-08-10 13:15 6459而OnPaint()是CWnd的类成员,同时负责响应WM_ ... -
listctrl 重绘
2010-08-10 10:15 1060common control 4.7版本介绍 ... -
OnCreate PreCreateWindow PreSubclassWindow
2010-08-09 17:47 1782OnCreate PreCreateWindow PreSub ... -
扩展CListCtrl实现颜色长度改变
2010-08-09 17:34 3231用CListCtrl来显示数据比较方便,有时候我们需要标注某一 ... -
创建CListCtrl中的排序小图标
2010-08-09 17:11 2325创建CListCtrl中的排序小图标 for(int i = ... -
CListCtrl控件的使用
2010-08-09 17:02 1205初始化: DWORD dwStyle; dwStyle = m ... -
改变 CListCtrl、CHeaderCtrl 高度、字体、颜色和背景
2010-08-09 16:49 2695改变 CListCtrl、CHeaderCtrl 高度、字体、 ... -
CHeaderCtrl用法
2010-08-09 15:47 4263CListCtrl的表头可以单独 ... -
CListCtrl::SortItems的用法:
2010-08-09 12:37 2012CListCtrl::SortItems的用法: (一)So ... -
树型视的三个结构TVINSERTSTRUCT、TVITEM、NMTREEVIEW
2010-08-05 18:11 996TVINSERTSTRUCT包含添加新项到树形视控件所使用的信 ... -
剪贴板和OLE拖放
2010-08-05 16:35 1164一、传统剪贴板 ... -
MFC一些函数区别
2010-08-05 13:43 1678GetMessagePos GetCursorPos的区别 ... -
LPTSTR、LPCSTR、LPCTSTR、LPSTR的意义
2010-08-05 13:33 1048UNICODE:它是用两个字节 ...
相关推荐
CFrameWnd是主框架窗口,它包含文档视图和菜单、工具栏等控件。我们可以通过修改其资源文件来定制应用程序的外观和功能,例如添加新的菜单项或工具栏按钮。 除了这些基础类,MFC还提供了一些其他关键类,如CWinApp...
通过深入理解文档/视图结构及其各个组成部分,开发者可以更高效地构建出功能完善且易于扩展的 Windows 应用程序。特别是对于 SDI 应用程序来说,掌握其基本框架是非常必要的,这有助于开发者更好地组织代码并提高...
### 深入浅出MFC文档视图结构 #### MFC文档/视图架构基本概念 Microsoft Foundation Classes (MFC) 是一个广泛应用于Windows平台的C++类库,为开发者提供了一系列高度封装的类来简化Windows编程。MFC的核心设计...
在深入分析MFC文档视图结构(项目实践)中,我们首先要理解文档视图结构(Document/View Architecture)的核心概念。MFC(Microsoft Foundation Classes)是微软提供的一个面向对象的C++库,用于构建Windows应用程序...
在Microsoft Foundation Classes (MFC)库中,多文档多视图编程是一种常见的应用程序设计模式,它允许用户同时处理多个相关的数据文档。这种模式基于Windows的MDI (Multiple Document Interface)架构,使得用户可以在...
在计算机编程领域,尤其是在Windows应用程序开发中,Visual C++是一个强大的工具,而文档视图结构是它的一个核心概念。这一章节主要讲解如何利用MFC(Microsoft Foundation Classes)库来构建基于文档/视图的程序...
在实现单文档多视图的过程中,通常会涉及到以下关键步骤和知识点: 1. **创建SDI应用程序框架**:首先,你需要创建一个基于MFC的SDI项目,这通常会在Visual Studio中通过向导完成。这将生成一个包含CWinApp、...
标题和描述中提到的“多视图的实现即一个文档有多个相关联的视图,不是分割视图”,这意味着系统提供了多种展示同一文档内容的方式,而这些视图之间是相互关联的,改变一个视图会影响到其他视图。 1. **多视图的...
然而,随着MFC的发展,特别是MFC 2.0版本的发布,文档/视结构的概念被引入,它将应用程序的数据存储和显示功能分离到不同的对象中,使得程序更加模块化。 在文档/视结构中,主要涉及到以下几个核心概念: - **文档...
在软件开发领域,基于文档视图的程序设计(Document-View Architecture)是一种常见的设计模式,尤其在Microsoft的MFC(Microsoft Foundation Classes)框架中被广泛使用。这种设计模式主要用于构建用户界面,它将...
在Microsoft Foundation Classes (MFC)框架中,"dlgtoview2.rar" 文件可能是一个示例项目,旨在演示如何在已有的基于对话框的应用程序中引入文档视图结构。MFC是一个C++库,它提供了对Windows API的封装,使得开发...
在Windows应用程序开发中,尤其是基于MFC(Microsoft Foundation Classes)框架的时候,我们经常需要处理“单文档多视图”(Single Document Multiple Views,SDMV)的设计模式。这种模式允许在一个文档中显示多个...
【标题】"vs2010文档视图Demo"是一个基于Visual Studio 2010的MFC(Microsoft Foundation Classes)工程示例,它展示了如何在应用程序中实现文档视图架构。MFC是微软提供的一套C++库,用于简化Windows应用程序的开发...
数据结构设计文档是软件开发过程中不可或缺的一部分,尤其是在构建数据库系统时。本文档以"网上花店数据库设计"为例,详细阐述了数据库设计的各个阶段,包括外部设计、结构设计和运用设计,旨在确保项目的顺利进行并...
向导会自动为你生成必要的框架代码,包括MDI主框架窗口和默认的文档/视图结构。 5. **菜单和命令处理**:在MDI应用程序中,菜单项通常会触发命令,这些命令可能作用于当前活动的MDI子窗口。你需要在你的主框架窗口...
这种视图非常适合于准备将文档发布到网络上的用户,因为它可以确保文档在网络环境下的显示效果与Word中的效果保持一致。在Web版式视图下,文档会被优化成适合在浏览器中查看的形式,便于发送电子邮件或创建网页内容...
在设计文档视图时,我们可以自定义列的显示方式,设置筛选条件,或者创建链接到其他文档或网页的快捷方式。 在"getDbDesign.nsf"这个文件中,很可能包含了用于展示如何设计和配置这种视图的实例。通常,这个文件...
《MFC编程实例 第11章到20章》涵盖了MFC编程的多个关键领域,这些章节深入浅出地解析了Microsoft Foundation Classes (MFC) 库在实际应用中的运用。MFC是Microsoft提供的一种C++类库,它简化了Windows API的使用,使...
MFC中的文档/视图架构是一种设计模式,用于处理数据(文档)和用户界面(视图)之间的关系。文档存储数据,视图负责显示和编辑文档,而框架类负责管理文档和视图的交互。 5. **MFC控件** MFC提供了丰富的控件类,...