MFC单文档和多文档框架

原文地址:李浩的博客 lihaohello.top


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

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

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

核心类

CWinApp - 程序类

  • 定义一个全局CWinApp对象,作为程序爆破点
  • 重写InitInstance()虚函数,在其中创建框架类对象
#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、自定义视图类
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 - 文档类

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

#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单文档程序

以下为简单的示例程序:

#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;

单步调试分析其流程:

// 构造单文档模板类对象
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多文档程序

以下为简单的示例程序:

#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()创建文档,程序会自动创建框架类对象、视图类对象和文档类对象

胸中丘壑:四大类关系

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值