MFC 文档类

发布时间:2023年12月31日

目录

文档类概述

文档类的使用

程序框架过程

LoadFrame执行分析

框架窗口 WM_CREATE 消息处理

视图窗口 WM_CREATE 消息处理

对象关系图

窗口切分

命令消息处理顺序

文档类和视图类关系


文档类概述

相关类CDocument,作用:提供了一个用于管理数据的类,封装了关于数据的管理(数据提取,数据转换,数据存储等),并和视图类进行数据交互。

文档类的使用

定义一个自己的文档类(CMyDoc ),派生自CDocument

#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument {
};

class CMyView : public CView {
	DECLARE_DYNCREATE(CMyView) //动态创建机制
	DECLARE_MESSAGE_MAP()
public:
	virtual void OnDraw(CDC* pDC);
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
BEGIN_MESSAGE_MAP(CMyView, CView)
	ON_WM_CREATE()
END_MESSAGE_MAP()
int CMyView::OnCreate(LPCREATESTRUCT pcs) {
	return CView::OnCreate(pcs); //将文档类对象和视图类对象建立关联关系。
}
void CMyView::OnDraw(CDC* pDC) {
	pDC->TextOut(100, 100, "我是视图窗口");
}



class CMyFrameWnd : public CFrameWnd {
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
	afx_msg void OnPaint();
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_PAINT()
	ON_WM_CREATE()
END_MESSAGE_MAP()

void CMyFrameWnd::OnPaint() {
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint(this->m_hWnd, &ps);
	::TextOut(hdc, 200, 200, "我是框架窗口客户区", strlen("我是框架窗口客户区"));
	::EndPaint(this->m_hWnd, &ps);
}
int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs) {
	return CFrameWnd::OnCreate(pcs);//动态创建视图类对象,并创建视图窗口
}



class CMyWinApp : public CWinApp {
public:
	virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance() {
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	CMyDoc* pDoc = new CMyDoc;

	CCreateContext cct;
	cct.m_pCurrentDoc = pDoc;//文档类对象地址
	cct.m_pNewViewClass = RUNTIME_CLASS(CMyView);//&CMyView::classCMyView  CRuntimeClass 成员变量

	pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);  // 内部依然是调用Create
	m_pMainWnd = pFrame;
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

程序框架过程

复习一下前面学习过的内容
WM_CREATE消息的两个附带信息

  • wParam-不使用
  • lParam -传来的信息为指针(CREATESTRUCT类型),这个指针指向位置保存了创建窗口函数( :CreateWindowEx )的全部12个参数

结合消息映射机制
ON_WM_CREATE()
处理WM_CREATE消息的函数为︰
afx_msg int OnCreate(LPCREATESTRUCT pcs);

流程如下:

  • 利用框架类对象地址( pFame )调用LoadFrame函数,创建框架窗口
  • 在处理框架窗口的WM_CREATE消息时,动态创建视图类对象,并创建视图窗口。
  • 在处理视图窗口的WM_CREATE消息时,将文档类对象和视图类对象建立关联关系。
    ?

LoadFrame执行分析

利用框架类对象地址( pFame )调用LoadFrame函数,创建框架窗口

给这个函数下断点,F11进入,this 指针是 pFrame,需要关注 CCreateContext 对象 cct

调用 Create 函数?

调用 CreateEx 函数

cct 被赋值给 cs.lpCreateParams

PreCreateWindow函数:注册窗口类 Afx……,处理 lpszClassName 为NULL

AfxHookWindowCreate函数:埋了一个钩子对WM_CREATE函数感兴趣,将pFrame保存到全局变量 程序线程信息 中

调用?CreateWindowEx 函数,最后一个参数 cs.lpCreateParam 是 cct

然后就创建了主框架窗口

框架窗口创建之后就会产生消息,WM-CREATE

框架窗口 WM_CREATE 消息处理

在处理框架窗口的WM_CREATE消息时,动态创建视图类对象,并创建视图窗口

这里是调用父类来实现的,函数内部this是 pFrame

LPCREATESTRUCT pcs;? ? pcs指向也就是上面调用? CreateWindowEx 函数的12个参数

有获取了 cct,随后进入 OnCreateHelper 函数,this指针依然是 pFrame

?进入函数 OnCreateClient

?进入函数 CreateView ,估计就是创建视图窗口的

pContext 就是 cct? ,m_pNewViewClass 是RUNTIME_CLASS ,就是CMyView类的成员变量 classCMyView这个结构体

使用动态创建机制创建 CMyView 对象 pView 并返回

创建视图窗口,调用Create函数,cct作为最后一个参数

调用函数? this=pView , 最后一个参数是 cct

再次回来

创建视图窗口,最后一个参数也是? cct

之后会产生 WM_CREATE 消息,会被钩子勾走,完成对象与句柄的绑定。然后钩子把消息还给视图窗口

视图窗口 WM_CREATE 消息处理

在处理视图窗口的WM_CREATE消息时,将文档类对象和视图类对象建立关联关系

pcs,就是 ::CreateWindowEx 函数的12个参数

进入if语句:

pContext 是cct,cct->m_pCurrentDoc 是CMyDoc对象的地址

AddView函数内部 this 指针是 CMyDoc 的对象,参数this是pView

进入函数,m_viewList 是啥,无声明定义多半前面有this指针,是某个类或者结构体的成员,应该是文档类的成员变量,是一个链表

m_viewList.AddTail(pView);? ? ?AddTail函数往链表尾部添加一个视图类对象地址,文档类对象用一个链表成员变量,保存视图类对象地址

pView->m_pDocument = this;? ?视图类对象的成员变量保存了文档类对象地址,视图类对象用一个普通成员变量,保存文档类对象地址

对象关系图

  • 应用程序类对象 theApp
  • theApp->m_pMainWnd = pFrame ,框架类对象地址
  • theApp->m_pMainWnd->m_pDocument =?pFrame->m_pDocument = pDoc,文档类对象地址pDoc
  • theApp->m_pMainWnd->m_pDocument->m_viewList =?pDoc->m_viewList,存储了所有视图类对象地址

文档类对象用一个链表成员变量,保存视图类对象地址视图类对象用一个普通成员变量,保存文档类对象地址。

经过分析可知一个文档类对象可以对应多个视图类对象(视图窗口),而一个视图类对象(视图窗口)只能对应一个文档类对象。

这样的话,就相当于

文档对象存储数据,可以对应多个视图对象,即展示数据;而数据对象只能对应一个文档对象

这样的功能该如何实现?

窗口切分

相关类:CSplitterWnd-不规则框架窗口类,封装了关于不规则框架窗口的操作。

注:规则框架窗口只有一个客户区,不规则框架窗口有多个客户区。

窗口切分的使用:

  • 重写CFrameWnd类的成员虚函数OnCreateClient
    • 在虚函数中调用CSplitterWnd:CreateStatic创建不规则框架窗口
    • 在虚函数中调CSplitterWnd::CreateView创建视图窗口。

窗口的关系:

  • 最底下是框架窗口的客户区
  • 上面一层是CSplitterWnd:CreateStatic创建不规则框架窗口,就是一个倒日型窗口,是框架出纳港口的子窗口
  • 最上面就是两个视图窗口,分别是上面的子窗口

OnCreateClient 是一个虚函数,在CFrameWnd的实现中只能创建一个视图窗口。可以在CMyFrameWnd类中重写这个虚函数,就可以创建多个视图窗口。

OnCreateClient 被调用的时机是在处理WM_CREATE函数时

调用CreateView上面已经分析过了:

  1. 创建视图对象
  2. 创建视图窗口
  3. 产生WM_CREATE消息,父类处理这个消息,实现了文档类和视图类的对象关联

m_pViewActive = (CView*)split.GetPane(0,1); // GetPane获取某一个视图类对象,0,1位置那个,设置为活动该窗口

#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument {
};

class CMyView : public CView {
	DECLARE_DYNCREATE(CMyView) //动态创建机制
	DECLARE_MESSAGE_MAP()
public:
	virtual void OnDraw(CDC* pDC);
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
BEGIN_MESSAGE_MAP(CMyView, CView)
	ON_WM_CREATE()
END_MESSAGE_MAP()
int CMyView::OnCreate(LPCREATESTRUCT pcs) {
	return CView::OnCreate(pcs); //将文档类对象和视图类对象建立关联关系。
}
void CMyView::OnDraw(CDC* pDC) {
	pDC->TextOut(100, 100, "我是视图窗口");
}



class CMyFrameWnd : public CFrameWnd {
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
	afx_msg void OnPaint();
	virtual BOOL OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext);
	CSplitterWnd split;//不规则框架窗口
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_PAINT()
	ON_WM_CREATE()
END_MESSAGE_MAP()
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext) {
	//创建两个视图窗口
	split.CreateStatic(this, 1, 2);// 参数1:父窗口,是框架窗口this;参数2,3:一行两列
	// 安装视图窗口 0行0列安装一个视图窗口,CSize没啥用,反正也不会遵守,最后一个参数是cct
	split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(100, 100), pContext); 
	// 安装视图窗口 0行1列安装一个视图窗口
	split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(100, 100), pContext);
	// 这个CtreateView函数拿着视图类对象调用类工厂函数 CreateObject,创建视图窗口对象,再创建窗口动态创建机制
	m_pViewActive = (CView*)split.GetPane(0,1); // GetPane获取某一个视图类对象,0,1位置那个,设置为活动该窗口
	return TRUE;
}
void CMyFrameWnd::OnPaint() {
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint(this->m_hWnd, &ps);
	::TextOut(hdc, 200, 200, "我是框架窗口客户区", strlen("我是框架窗口客户区"));
	::EndPaint(this->m_hWnd, &ps);
}
int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs) {
	return CFrameWnd::OnCreate(pcs);//动态创建视图类对象,并创建视图窗口
}



class CMyWinApp : public CWinApp {
public:
	virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance() {
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	CMyDoc* pDoc = new CMyDoc;

	CCreateContext cct;
	cct.m_pCurrentDoc = pDoc;//文档类对象地址
	cct.m_pNewViewClass = RUNTIME_CLASS(CMyView);//&CMyView::classCMyView  CRuntimeClass 成员变量

	pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);  // 内部依然是调用Create
	m_pMainWnd = pFrame;
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}

CMyWinApp theApp;

命令消息处理顺序

对象关系图:

在下图中框架类对象的成员变量m_pViewActive保存了一个视图类对象的地址,在上面框架窗口有多个视图窗口,那么它保存的是那个?

它保存的是活动窗口,就是鼠标点击某个窗口后,这个窗口就是活动窗口,这个窗口只能有一个。也就是鼠标点击后就会触发消息,保存这个窗口对象地址保存到成员变量中。

消息映射机制:圆形代表结构体静态变量? ? ?方形代表消息映射的数组静态变量

最下面的四个类是我们创建的,本身是没有这两个静态成员变量的,需要我们自己添加

WM_COMMAND处理顺序:视图类→文档类→框架类→应用程序类(默认顺序)

其他消息找自己窗口的线上处理。

#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument {
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnNew();
	CString str;
};
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
	ON_COMMAND(ID_NEW, OnNew)
END_MESSAGE_MAP()
void CMyDoc::OnNew() {
	this->str = "hello world"; //接受到的数据。
//	this->UpdateAllViews( NULL );//刷新和这个文档类对象(this)关联的所有视图窗口
	//this->m_viewList;
	POSITION pos = this->GetFirstViewPosition(); //GetFirstXXXPosition
	CView* pView = this->GetNextView(pos);   //GetNextXXX
	this->UpdateAllViews(pView);//刷新和这个文档类对象(this)关联的除了pView指向的视图窗口
}
class CMyView : public CView {
	DECLARE_DYNCREATE(CMyView) //动态创建机制
	DECLARE_MESSAGE_MAP()
public:
	virtual void OnDraw(CDC* pDC);
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
	afx_msg void OnNew();
};
IMPLEMENT_DYNCREATE(CMyView, CView)
BEGIN_MESSAGE_MAP(CMyView, CView)
	//	ON_COMMAND( ID_NEW, OnNew )
	ON_WM_CREATE()
END_MESSAGE_MAP()
void CMyView::OnNew() {
	AfxMessageBox("视图类处理的WM_COMMAND消息");
}
int CMyView::OnCreate(LPCREATESTRUCT pcs) {
	return CView::OnCreate(pcs); //将文档类对象和视图类对象建立关联关系。
}
void CMyView::OnDraw(CDC* pDC) {
	//	CMyDoc* pDoc = (CMyDoc*)this->m_pDocument;
	CMyDoc* pDoc = (CMyDoc*)this->GetDocument();
	pDC->TextOut(100, 100, pDoc->str);
}

class CMyFrameWnd : public CFrameWnd {
	DECLARE_MESSAGE_MAP()
public:
	afx_msg int OnCreate(LPCREATESTRUCT pcs);
	afx_msg void OnPaint();
	virtual BOOL OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext);
	CSplitterWnd split;//不规则框架窗口
	afx_msg void OnNew();
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	//	ON_COMMAND( ID_NEW, OnNew)
	ON_WM_PAINT()
	ON_WM_CREATE()
END_MESSAGE_MAP()
void CMyFrameWnd::OnNew() {
	AfxMessageBox("框架类处理了新建被点击");
}
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext) {
	//创建两个视图窗口
	split.CreateStatic(this, 1, 2);
	split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(100, 100), pContext);
	split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(100, 100), pContext);
	m_pViewActive = (CView*)split.GetPane(0, 0);
	return TRUE;
}
void CMyFrameWnd::OnPaint() {
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint(this->m_hWnd, &ps);
	::TextOut(hdc, 200, 200, "我是框架窗口客户区", strlen("我是框架窗口客户区"));
	::EndPaint(this->m_hWnd, &ps);
}
int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs) {
	return CFrameWnd::OnCreate(pcs);//动态创建视图类对象,并创建视图窗口
}
class CMyWinApp : public CWinApp {
	DECLARE_MESSAGE_MAP()
public:
	virtual BOOL InitInstance();
	afx_msg void OnNew();
};
BEGIN_MESSAGE_MAP(CMyWinApp, CWinApp)
	//	ON_COMMAND( ID_NEW, OnNew )
END_MESSAGE_MAP()
void CMyWinApp::OnNew() {
	AfxMessageBox("应用程序类处理了WM_COMMAND消息");
}
BOOL CMyWinApp::InitInstance() {
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	CMyDoc* pDoc = new CMyDoc;

	CCreateContext cct;
	cct.m_pCurrentDoc = pDoc;//文档类对象地址
	cct.m_pNewViewClass = RUNTIME_CLASS(CMyView);//&CMyView::classCMyView

	pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);
	m_pMainWnd = pFrame;
	pFrame->ShowWindow(SW_SHOW);
	pFrame->UpdateWindow();
	return TRUE;
}
CMyWinApp theApp;



在应用程序类下断点分析可以观察到整个流程

查看调用堆栈?

WM_COMMAND 消息 与 WM_CREATE 消息是在 OnWndMsg 中分道扬镳

接下来进入 OnCommand 函数,但是这个函数的 this 指针是谁?应该是 pFrame 框架窗口对象

所以在函数OnCommand函数中的this指针也是pFrame

进入函数?OnCommand 函数,之后进入CFrameWnd::OnCmdMsg,内部this指针依然是pFrame

下面是?CFrameWnd::OnCmdMsg 函数内部的处理

CView* pView = GetActiveView();? this指针是pFrame,根据类之间的关系,框架类对象中保存了视图类对象,然后分别进入三条消息映射机制支脉去处理WM_COMMAND消息”

  1. 进入视图类的消息映射机制支脉中处理消息
  2. 进入框架类的消息映射机制支脉中处理消息
  3. 进入应用程序类的消息映射机制支脉中处理消息

消息只处理一次

进入 pView:OnCmdMsg 函数中,之后进入?CWnd::OnCmdMsg,内部this是pView

因为视图类没有处理函数,3次循环之后返回FALSE退出

进入文档类支脉,this 指针是pDoc

遍历文档类支脉,3次循环后返回FALSE退出

之后返回FALSE,回到起点

this指针是pFrame,进入框架类对象支脉,之后的流程和上面差不多

?this指针是pApp,进入应用程序类对象支脉,之后的流程和上面差不多,找到对应的处理函数

然后调用WM_COMMAND消息处理函数处理消息

另外这个处理顺序函数是一个虚函数,是可以重写,自定义顺序的。

文档类和视图类关系

文档类数据发生变化,刷新视图窗口,都会产生重绘消息

CMyDoc::OnNew()

void CMyDoc::OnNew() {
	this->str = "hello world"; //接受到的数据。
//	this->UpdateAllViews( NULL );//刷新和这个文档类对象(this)关联的所有视图窗口
	//this->m_viewList;
	POSITION pos = this->GetFirstViewPosition(); //GetFirstXXXPosition
	CView* pView = this->GetNextView(pos);   //GetNextXXX
	this->UpdateAllViews(pView);//刷新和这个文档类对象(this)关联的除了pView指向的视图窗口
}

CMyView::OnDraw(CDC* pDC)?

void CMyView::OnDraw(CDC* pDC) {
	//	CMyDoc* pDoc = (CMyDoc*)this->m_pDocument;
	CMyDoc* pDoc = (CMyDoc*)this->GetDocument();
	pDC->TextOut(100, 100, pDoc->str);
}

视图类成员函数:获取和视图类对象关联的文档类对象,调用GetDocument(
文档类成员函数:当文档类数据发生变化时,调用UpDataAllViews刷新和文档类对象相关联的视图类对象(视图窗口)


?

文章来源:https://blog.csdn.net/qq_61553520/article/details/135181917
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。