MFC - 03 视图、文档
视图窗口
图中的圆和框代表了宏定义展开后的静态变量和消息处理数组。
覆盖在主框架客户区的窗口,提供了一个专门用于显示数据的窗口。当不创建 CView 对象时,在窗口客户区看见的就是客户区,不是视图类。
相关类:CView 及其相关子类,父类为CWnd类,封装了关于视图窗口的各种操作,以及和文档类的数据交互
使用:
- 定义一个自己的视图类(CMyView),派生自 CView,并重写父类成员纯虚函数
OnDraw
(可用于绘图) - 其余框架类和应用程序类代码不变
- 处理主框架窗口的
WM_CREATE
消息时,定义 CMyView 类对象,并调用 Create 函数创建视图窗口。
视图窗口ID为AFX_IDW_PANE_FIRST
#include <afxwin.h>
class CMyView : public CView
{
public:
void OnDraw(CDC* pDc);
};
void CMyView::OnDraw(CDC* pDC)
{
pDC->TextOutA(100, 100, "CMyView::OnDraw"); // 父类中用于处理 WM_PAINT 的纯虚函数
}
// --------------------------------------------------------------
class CMyFrameWnd : public CFrameWnd
{
DECLARE_MESSAGE_MAP()
public:
afx_msg int OnCreate(LPCREATESTRUCT);
private:
CMyView *pView;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
int CMyFrameWnd::OnCreate(LPCREATESTRUCT lps)
{
this->pView = new CMyView;
pView->Create(
NULL,
"MFCView", // 窗口标题,View窗口不会显示,故窗口标题无所谓
WS_CHILD | WS_VISIBLE | WS_BORDER, // 显示风格
CRect(0, 0, 200, 200), // View 窗口大小,大小随父窗口变化时此参数无用
this, // 父窗口指针
1001 // 窗口ID,大于等于 AFX_IDW_PANE_FIRST 时大小随父窗口变化
// 当窗口框架内有不止一个View时,其他ID递增 AFX_IDW_PANE_FIRST+1 AFX_IDW_PANE_FIRST+2
);
return CFrameWnd::OnCreate(lps);
}
// --------------------------------------------------------------
class CMyWinApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd* pFrame = new CMyFrameWnd;
this->m_pMainWnd = pFrame;
pFrame->Create(NULL, "title");
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CMyWinApp theApp;
结果:
命令消息处理顺序
- 视图类
- 框架类
- 应用程序类
对象关系
应用程序对象持有框架类对象地址,框架类对象持有视图对象地址。
文档类
CDocument
提供了一个用于管理数据的类,封装了关于数据的管理(数据提取,数据转换,数据存储等),并和视图类进行数据交互。
程序创建过程
- 利用框架类对象地址(pFrame)调用LoadFrame函数,创建框架窗口
- 处理框架窗口的
WM_CREATE
消息时,动态创建视图类对象,并创建视图窗口
视图类实现了动态创建,由框架类的父类 CFrameWnd 动态创建视图类 - 处理视图窗口的
WM_CREATE
消息时,将文档类对象和视图类对象建立关联关系
查看代码
#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument
{
};
// ---------------------------------------------------
class CMyView : public CView
{
DECLARE_DYNCREATE(CMyView) // 动态创建
public:
virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView) // 实现动态创建
void CMyView::OnDraw(CDC* pDC)
{
}
// ---------------------------------------------------
class CMyFrameWnd : public CFrameWnd
{
};
// ---------------------------------------------------
class CMyWinApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd* pFrame = new CMyFrameWnd;
CCreateContext cct;
cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
cct.m_pCurrentDoc = new CMyDoc;
pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);
this->m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CMyWinApp theApp;
文档类对象用一个链表成员变量保存视图类对象地址:一对多
视图类对象用一个普通成员变量保存文档类对象地址:多对一
在视图类窗口中处理 WM_COMMAND 消息时要先点击以下某个视图类,让 pFrame->m_pViewActive 被赋值,然后视图类窗口才能够处理消息。
手动激活视图窗口的方式:
// 重写的用于创建不规则框架类和视图类的虚函数 BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext) { this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,两个视图相同 this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext); this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext); // 通过不规则框架类获得指定位置窗口,并将其设置为激活窗口 this->m_ViewAcitve = (CView*)split.GetPane(0,0); return TRUE; }
刷新窗口
当文档类数据发生变化时,调用 UpdateAllViews
刷新和文档类对象关联的视图类对象(视图窗口)。也就是向视图窗口发送绘图消息。
// Update Views (simple update - DAG only)
void UpdateAllViews(
CView* pSender,
LPARAM lHint = 0L,
CObject* pHint = NULL
);
刷新窗口的例子:
查看代码
#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument
{
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnFileNew();
CString str = "default string";
};
BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
END_MESSAGE_MAP()
void CMyDoc::OnFileNew()
{
str = "new string";
//this->UpdateAllViews(NULL); // 刷新所有窗口,参数传递的窗口不会被刷新
POSITION pos = this->GetFirstViewPosition(); // 获得 viewList 迭代器的头节点的前一个
CView* pView = this->GetNextView(pos); // 得到第一个窗口
this->UpdateAllViews(pView); // 刷新第一个窗口以外的窗口
}
class CMyView : public CView
{
DECLARE_DYNCREATE(CMyView)
public:
virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
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
{
public:
virtual BOOL OnCreateClient(LPCREATESTRUCT, CCreateContext*);
private:
CSplitterWnd split;
};
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext)
{
this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,两个视图相同
this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext);
this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext);
// 设置活动窗口
this->m_pViewActive = (CView*)this->split.GetPane(0, 0);
return TRUE;
}
class CMyWinApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd* pFrame = new CMyFrameWnd;
CCreateContext cct;
cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
cct.m_pCurrentDoc = new CMyDoc;
pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);
this->m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CMyWinApp theApp;
文档类和视图类的关系
- 视图类成员函数获取关联的文档类对象,调用
GetDocument()
- 当文档类数据发生变化时,调用
UpdateAllView()
刷新和文档类对象关联的视图类对象(视图窗口)
获取视图对象并刷新:
POSITION pos = this->GetFirstViewPosition(); // 获得 viewList 迭代器的头节点的前一个 CView* pView = this->GetNextView(pos); // 得到第一个窗口 this->UpdateAllViews(pView); // 刷新第一个窗口以外的窗口
窗口切分
不规则窗口:有多个视图类
CSplitterWnd
:不规则框架窗口类,封装了关于不规则框架窗口的操作
窗口切分的操作:
重写 CFrameWnd
类的成员虚函数 OnCreateClient
在虚函数中调用 CSplitterWnd::CreateStatic
创建不规则框架窗口,调用 CSplitterWnd::CreateView
创建视图窗口
结构如下:
主框架窗口的客户区覆盖了一个不规则框架窗口,这个窗口分出了两个视图窗口。
#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
class CMyDoc : public CDocument
{
};
// ---------------------------------------------------
class CMyView : public CView
{
DECLARE_DYNCREATE(CMyView) // 动态创建
public:
virtual void OnDraw(CDC*);
};
IMPLEMENT_DYNCREATE(CMyView, CView) // 实现动态创建
void CMyView::OnDraw(CDC* pDC)
{
pDC->TextOutA(100, 100, "View窗口输出的字符串");
}
// ---------------------------------------------------
class CMyFrameWnd : public CFrameWnd
{
DECLARE_MESSAGE_MAP()
public:
virtual BOOL OnCreateClient(LPCREATESTRUCT, CCreateContext*);
private:
CSplitterWnd split;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
END_MESSAGE_MAP()
BOOL CMyFrameWnd::OnCreateClient(LPCREATESTRUCT pcs, CCreateContext* pContext)
{
this->split.CreateStatic(this, 1, 2); // 创建1行2列的布局,下面两个视图相同
this->split.CreateView(0, 0, RUNTIME_CLASS(CMyView), CSize(0, 0), pContext);
this->split.CreateView(0, 1, pContext->m_pNewViewClass, CSize(0, 0), pContext);
return TRUE;
}
// ---------------------------------------------------
class CMyWinApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd* pFrame = new CMyFrameWnd;
CCreateContext cct;
cct.m_pNewViewClass = RUNTIME_CLASS(CMyView); // 拿到 &CMyView::classCMyView 用于动态创建
cct.m_pCurrentDoc = new CMyDoc;
pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);
this->m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CMyWinApp theApp;
这两个视图对象对应一个文档类。对象视图类对象创建时会产生 WM_CREATE 消息,处理函数会将二者关联。
WM_COMMAND 消息处理顺序
处理顺序,在其中哪一个处理都可以:
- 视图类
- 文档类
- 框架类
- 应用程序类
// 主要对象组织顺序
theApp
m_PMainWnd // 框架类对象地址
m_pViewActive // 活动视图类对象地址 pView
m_pDocument // 文档类对象地址 pDoc
m_viewList // 所有视图类对象地址
图中的圈指宏展开后的静态变量,框指消息处理函数映射表。
在处理 WM_COMMAND
消息时,进入框架消息处理函数,调用虚函数 CFrameWnd::OnCmdMsg
(可重写),内部先获得活动视图类对象调用其消息处理函数,然后获得视图绑定的文档类的消息处理函数,再查找自己的消息处理函数,最后通过全局变量获取应用程序类再获取其的消息处理函数。