MFC单文档和多文档框架

MFC曾在Windows桌面开发领域大放异彩,但随着时间的推移,如今相比各种流行的GUI开发技术(QT、WPF、Flutter、Web技术等),MFC被贴上“古老”、“落后”的标签。

但有大量工业软件采用MFC进行开发,且在AutoCAD二次开发领域,MFC是官方指定的ObjectARX界面开发方式,掌握MFC技术具有一定的现实需求。

本文从整体上介绍MFC单文档和多文档应用程序的整体框架。

核心类

CWinApp - 程序类

  • 定义一个全局CWinApp对象,作为程序爆破点
  • 重写InitInstance()虚函数,在其中创建框架类对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<afxwin.h>
// 自定义程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() override;
};
BOOL CMyWinApp::InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, "MFCView");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
// 程序爆破点
CMyWinApp theApp;

CFrameWnd - 框架类

  • 作为容纳菜单、工具条、状态条、视口等元素的容器
  • 在其WM_CREATE事件处理函数中创建视图类的对象等

CView - 视图类

使用步骤:

  • 自定义程序类和框架类
  • 自定义视图类,派生自CView,重写纯虚函数OnDraw(),用于绘制视图
  • 在自定义框架类中的WM_CREATE消息处理函数中,调用Create()创建视图窗口,如果指定窗口ID为AFX_IDW_PANE_FIRST,则按照框架窗口大小确定视图大小
  • OnDraw()虚函数由父类的WM_PAINT事件处理函数来调用,优先使用该方法来实现视图的绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 1、自定义视图类
class CMyView :public CView {
virtual void OnDraw(CDC* pDC) override;
};
// 如果重新重新处理WM_PAINT消息,那么就使用OnDraw绘图!
void CMyView::OnDraw(CDC* pDC) {
pDC->TextOut(50, 50, "我是CMyView", strlen("我是CMyView"));
}
// 自定义框架类
class CMyFrameWnd :public CFrameWnd {
private:
CMyView* pView;
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnPaint();
afx_msg int OnCreate(LPCREATESTRUCT pcs);
};
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, 100, 100, "我是CMyFrameWnd", strlen("我是CMyFrameWnd"));
::EndPaint(this->m_hWnd, &ps);
}
int CMyFrameWnd::OnCreate(LPCREATESTRUCT pcs) {
// 2、创建视口类对象
pView = new CMyView;
pView->Create(NULL, "MFCView", WS_CHILD | WS_VISIBLE | WS_BORDER, CRect(0, 0, 200, 200), this, AFX_IDW_PANE_FIRST);
return CFrameWnd::OnCreate(pcs);
}

CDocument - 文档类

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <afxwin.h>
#include <afxext.h>
#include "resource.h"
// 自定义文档类
class CMyDoc :public CDocument {};
// 自定义视图类
class CMyView :public CView {
DECLARE_DYNAMIC(CMyView)
virtual void OnDraw(CDC* pDC) override;
};
IMPLEMENT_DYNAMIC(CMyView, CView)
void CMyView::OnDraw(CDC* pDC) {
pDC->TextOut(50, 50, "我是CMyView", strlen("我是CMyView"));
}
// 自定义框架类
class CMyFrameWnd :public CFrameWnd { };
// 自定义程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance() override;
};
BOOL CMyWinApp::InitInstance() {
CMyFrameWnd* pFrame = new CMyFrameWnd;
CMyDoc* pDoc = new CMyDoc;
// 这里是重点
CCreateContext cct;
cct.m_pCurrentDoc = pDoc;
cct.m_pNewViewClass = RUNTIME_CLASS(CMyView);
// 创建框架窗口和视图窗口,并将视图与文档类绑定在一起
pFrame->LoadFrame(IDR_MENU1, WS_OVERLAPPEDWINDOW, NULL, &cct);
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
// 程序爆破点
CMyWinApp theApp;

文档类和视图类的关系

  • 调用GetDocument()可以获得与视图关联的文档对象
  • 当文档类数据发生变化时,调用文档类对象的UpdateAllViews()函数刷新和文档类对象关联的所有视图类对象

ON_COMMAND消息处理优先级

视图类 > 文档类 > 框架类 > 程序类,该过程由代码逻辑决定。

手动创建MFC单文档程序

以下为简单的示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <afxwin.h>
#include "resource.h"
// 创建文档类
class CMyDoc :public CDocument {
DECLARE_DYNCREATE(CMyDoc)
};
IMPLEMENT_DYNCREATE(CMyDoc, CDocument)

// 创建视图类
class CMyView :public CView {
DECLARE_DYNCREATE(CMyView)
public:
virtual void OnDraw(CDC* pDC);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
void CMyView::OnDraw(CDC* pDC) {
pDC->TextOut(100, 100, _T("我是视图窗口"));
pDC->MoveTo(200, 200);
pDC->LineTo(300, 200);
pDC->LineTo(300, 300);
}

// 创建框架类
class CMyFrameWnd :public CFrameWnd {
DECLARE_DYNCREATE(CMyFrameWnd)
};
IMPLEMENT_DYNCREATE(CMyFrameWnd, CFrameWnd)

// 创建应用程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance() {
// 创建单文档应用程序模板
CSingleDocTemplate* pTemplate = new CSingleDocTemplate(
IDR_MENU1,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyFrameWnd),
RUNTIME_CLASS(CMyView)
);
// 添加模板
AddDocTemplate(pTemplate);
// 根据模板创建新文档
OnFileNew();
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
// 程序启动的爆破点
CMyWinApp theApp;

单步调试分析其流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 构造单文档模板类对象
CSingleDocTemplate* pTemplate = new CSingleDocTemplate(
IDR_MENU1,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyFrameWnd),
RUNTIME_CLASS(CMyView)){
CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass);
// pTemplate->m_pOnlyDoc=NULL
m_pOnlyDoc = NULL;
}

// 将文档模板添加到theApp.m_pDocManager.m_templateList链表中
theApp.AddDocTemplate(pTemplate){
if (theApp.m_pDocManager == NULL){
// 初始化程序的文档管理器
theApp.m_pDocManager = new CDocManager;
}
// 将该模板添加到文档管理器
theApp.m_pDocManager->AddDocTemplate(pTemplate){
m_pDocManager.m_templateList.AddTail(pTemplate);
}
}

// 创建新文档
OnFileNew(){
m_pDocManager->OnFileNew(){
// 获取模板对象指针
CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetHead();
// 添加文档
pTemplate->OpenDocumentFile(NULL){
OpenDocumentFile(lpszPathName, TRUE, bMakeVisible){
CDocument* pDocument = NULL;
CFrameWnd* pFrame = NULL;
pDocument = CreateNewDocument(){
// 创建文档类对象
CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject();
AddDocument(pDocument){
CDocTemplate::AddDocument(pDoc){
// 为文档绑定模板类对象地址
pDoc->m_pDocTemplate = this;
}
m_pOnlyDoc = pDoc;
}
}
// 创建框架类对象,pDocument为刚刚创建的文档类对象
pFrame = CreateNewFrame(pDocument, NULL){
CCreateContext context;
context.m_pCurrentDoc = pDoc;
context.m_pNewViewClass = m_pViewClass;
// 接下来创建框架和视图,并将视图和文档类对象关联起来
CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
pFrame->LoadFrame(... , &context)){
pFrame->Create(..., &context){
pFrame->CreateEx(..., &context){
CREATESTRUCT cs;
...
cs.lpCreateParams=lpParam;
// 为窗体类名赋值,注册窗体类
PreCreateWindow(cs);
// 设置钩子,设置窗口处理函数,绑定窗体指针和句柄
AfxHookWindowCreate(this);
// 这里会激发钩子程序,设置好窗体处理函数,在转向WM_CREATE事件响应函数中
CreateWindowEx(..., cs.lpCreateParams);
}
}
}
}
}
}
}
}

OnCreate(LPCREATESTRUCT lpcs){
CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;
OnCreateHelper(lpcs, pContext){
// 这是一个虚函数,可以重写以支持自己创建视图窗体
OnCreateClient(lpcs, pContext){
CreateView(pContext, AFX_IDW_PANE_FIRST){
CWnd* pView = (CWnd*)pContext->m_pNewViewClass->CreateObject();
// 创建视图窗体
pView->Create(..., pContext);
}
}
}
}

先定义视图类、文档类和主框架类(支持动态创建机制),在应用程序类的InitInstance()中:

  • 创建一个单文档模板,并添加到文档管理器的模板列表中
  • 调用OnFileNew()创建文档,程序会自动创建框架类对象、视图类对象和文档类对象。

手动创建MFC多文档程序

以下为简单的示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <afxwin.h>
#include "resource.h"

// 创建文档类
class CMyDoc :public CDocument {
DECLARE_DYNCREATE(CMyDoc)
};
IMPLEMENT_DYNCREATE(CMyDoc, CDocument)

// 创建视图类
class CMyView :public CView {
DECLARE_DYNCREATE(CMyView)
public:
virtual void OnDraw(CDC* pDC);
};
IMPLEMENT_DYNCREATE(CMyView, CView)
void CMyView::OnDraw(CDC* pDC) {
pDC->TextOut(100, 100, _T("我是视图窗口"));
pDC->MoveTo(200, 200);
pDC->LineTo(300, 200);
pDC->LineTo(300, 300);
}

// 创建子框架类
class CMyChildWnd :public CMDIChildWnd {
DECLARE_DYNCREATE(CMyChildWnd)
};
IMPLEMENT_DYNCREATE(CMyChildWnd, CMDIChildWnd)

// 创建主框架类
class CMyFrameWnd :public CMDIFrameWnd {};

// 创建应用程序类
class CMyWinApp :public CWinApp {
public:
virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance() {
// 创建多文档模板
CMultiDocTemplate* pTemplate = new CMultiDocTemplate(
IDR_MENU2,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyChildWnd),
RUNTIME_CLASS(CMyView)
);
// 添加模板
AddDocTemplate(pTemplate);

// 创建主框架
CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->LoadFrame(IDR_MENU1);
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();

// 三次新建,每次创建都会一组新的(子框架类对象、文档类对象、视图类对象),这就是“单”、“多”的核心区别
OnFileNew();
OnFileNew();
OnFileNew();

return TRUE;
}
// 程序启动的爆破点
CMyWinApp theApp;

多文档与单文档应用程序的创建基本一致,只在某些细节上存在差异,不再单步调试跟踪。先定义视图类、文档类、子框架类(支持动态创建机制),在应用程序类中的InitInstance()中:

  • 创建一个多文档模板,并添加到文档管理器的模板列表中
  • 创调用OnFileNew()创建文档,程序会自动创建框架类对象、视图类对象和文档类对象

胸中丘壑:四大类关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
theApp: 程序全局对象
m_pMainWnd: 存储框架类对象的地址
m_pViewActive: 存储当前活动视图对象的地址
m_pDocument: 文档类对象地址pDoc
m_viewList: 文档的视图对象数组地址
m_pDocManager: 文档管理器
m_templateList: 模板链表pTemplates,单文档和多文档模板存在区别,见后

// 对于单文档模板对象
pTemplate: 单文档模板对象
m_pOnlyDoc: 存储唯一的文档对象
m_pDocClass: RUNTIME_CLASS(CMyDoc) // 先创建这个
m_pFrameClass: RUNTIME_CLASS(CMyFrameWnd)
m_pViewClass: RUNTIME_CLASS(CMyView)

// 对于单文档模板对象
pTemplate: 多文档模板对象
m_docList: 存储多个文档对象的地址
m_pDocClass: RUNTIME_CLASS(CMyDoc)
m_pFrameClass: RUNTIME_CLASS(CMyFrameWnd)
m_pViewClass: RUNTIME_CLASS(CMyView)

评论