MFC学习笔记

一、MFC编程基础

MFC的概念和作用

1.什么是MFC
MFC(Microsoft Foundation Class Library),全称为微软基础类库。它封装了Windows应用程序的各种API以及相关机制的C++类库。
2.MFC的作用
基于应用程序框架之上,使用提供的类库快速开发,提高项目的开发效率,减少开发周期和成本。

常用的头文件

  • afx.h-将各种MFC头文件包含在内;
  • afxwin.h-包含了各种MFC窗口类,如afx.h和windows.h;
  • afxext.h-提供了扩展窗口类的支持,如工具栏、状态栏等。

MFC的控制台程序

  • CWinApp theApp 这个全局对象控制程序的执行流程;
  • main函数不同于普通的控制台程序(详见程序启动机制);
  • 以Afx开头的函数为MFC库中的全局函数;
  • :: 开头的函数为win32的API函数。

MFC的库程序

  • 使用MFC制作静态库程序;
  • MFC的动态库:
    • 使用静态的MFC库制作动态库程序;
    • 使用动态的MFC库制作动态库程序;
  • 扩展库
    规则库可以被各种程序所调用,扩展库只能被MFC程序调用。

MFC的窗口程序

1.相关函数

  • 单文档视图架构程序
    CWinApp - 应用程序类,负责管理应用程序的流程;
    CFrameWnd - 框架窗口类,负责管理框架窗口;
    CView - 视图窗口类,负责显示数据;
    CDocument - 文档类,负责管理数据。
  • 多文档视图架构程序
    CWinApp - 应用程序类,负责管理应用程序的流程;
    CMDIFrameWnd -多文档主框架窗口类,负责管理主框架窗口;
    CMDIChildWnd -多文档子框架窗口类,负责管理子框架窗口;
    CView - 视图窗口类,负责显示数据;
    CDocument - 文档类,负责管理数据。
  • 对话框应用程序
    CWinApp - 应用程序类,负责管理应用程序的流程;
    CDialog - 对话框窗口类,负责管理对话框窗口。

2.MFC库中相关类简介

  • CObject
    MFC类库中绝大部分类的父类,提供了MFC类库中一些基本的机制;
    对运行时类信息的支持;
    对动态创建的支持;
    对序列化的支持;
  • CWinApp - 应用程序类
    该类是一个基类,封装了应用程序、线程等信息,通过它来继承Windows应用程序对象。应用程序对象提供了初始化应用程序(以及它的每一个实例)和运行应用程序所需的成员函数。
  • CDocument - 文档类
    为用户定义的文档类提供了基本的函数功能,来管理数据。
  • Frame WIndows - 框架窗口类
    封装了窗口程序组成的各种框架窗口处理函数。
  • CSplitterWnd - 拆分窗口类
    该类提供一个分隔窗口的功能。
  • Control Bars - 控件条类
    CContorlBar类是所有控件条类的基类,封装了各种控件资源。
  • Dialog Boxes - 对话框类
    CDialog类是在屏幕上显示的对话框基类,封装了各种对话框资源。
  • Views - 视图类
    CView类为用户定义的视图类提供了基本的功能,封装了各种显示窗口的资源。
  • Controls - 控件类
    封装了各种常用的控件。
  • Exceptions - 异常处理类
    CException是Microsoft基本类库中处理各种异常的基础库,封装了MFC中常用的各种异常处理方法。
  • File Services - 文件类
    CFile是MFC文件类的基类,它直接提供非缓冲的二进制磁盘输入/输出设备,并直接地通过派生类支持文本文件和内存文件,封装了各种文件的I/O操作。
  • 绘图类
    包括CDC类和CGDIObject类,为各种Windows图形设备接口(GDI)对象提供了一些基本类。
  • 数据集合类
    CArray/Clist/CMap…,封装了相应的数据结构的管理资源。
  • 非CObject类的子类
    提供了各种数据结构相关的管理实现,如CPoint、CTIME、CString等。

二、程序启动机制

WinMain入口函数

与Win32窗口程序相同,都是从WinMain入口,但是Win32中的WinMain函数由程序员自己实现,流程由程序员自己安排,而MFC库中已经实现了WinMain函数,程序的流程由MFC负责,所以在程序中不需要程序员自己实现。

CWinApp的成员

成员变量
m_pMainWnd:当前应用程序的主窗口
成员虚函数
InitInstance:程序的初始化函数,完成了窗口创建等初始化处理;
ExitInstance:程序退出时调用,清理资源等善后工作;
Run:消息循环;
OnIdle:空闲处理函数。

执行流程

  1. 程序启动,构造theApp对象,调用父类CWinApp的构造函数:
    • 将theApp对象的地址保存到线程状态信息中;
    • 将theApp对象的地址保存到模块状态信息中;
    • 进入WinMain函数,调用AfxWinMain函数。
  2. 进入入口函数WinMain:
    • 获取应用程序类对象theApp的地址;
    • 利用theApp地址调用InitApplication,初始化当前应用程序的数据;
    • 利用theApp地址调用InitInstance函数初始化程序,在函数中创建窗口并显示;
    • 利用theApp地址调用CWinApp的Run函数进行消息循环:
      • 若没有消息,利用theApp地址调用OnIdle虚函数实现空闲处理;
      • 若获取WM_QUIT消息,退出程序。
    • 程序退出,利用theApp地址调用ExitInstance虚函数实现退出后析构等善后处理工作。

程序 伪代码 如下:

/*
AFX_MODULE_STATE aaa;//当前程序模块状态信息
AFX_MODULE_THREAD_STATE bbb; //当前程序线程状态信息
*/

CWinApp::CWinApp()//构造全局对象CMyWinApp theApp
{
	//获取全局变量&aaa
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	//获取全局变量&bbb
	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;

	//将&theApp保存到bbb的一个成员中
	pThreadState->m_pCurrentWinThread = this;
	AfxGetThread()
	{
	 AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
	 CWinThread* pThread = pState->m_pCurrentWinThread;
	 return pThread;//返回的为&theApp
	}
	//将&theApp保存到aaa的一个成员中
	pModuleState->m_pCurrentWinApp = this;
	AfxGetApp()
	{
	return AfxGetModuleState()->m_pCurrentWinApp;//返回&theApp
	}
}

WinMain(...)//程序流程是不是theApp对象指导向哪走
{
	AfxWinMain(...)
	{
		CWinThread* pThread = AfxGetThread();
		CWinApp* pApp = AfxGetApp();//这两代码都是获取&theApp地址

		pApp->InitApplication();//利用theApp对象调用应用程序类成员虚函数 初始化
		pThread->InitInstance();//利用theApp对象调用应用程序类成员虚函数 创建并显示窗口
		pThread->Run()//利用theApp对象调用应用程序类成员虚函数 消息循环
		{
			for (;;)
			{
				while(没有消息时)
				  OnIdle(..);//利用theApp对象调用应用程序类成员虚函数 空闲处理
				do{
					if(GetMessage抓到WM_QUIT)
					return ExitInstance();//程序结束前,利用theApp对象调用应用程序类成员虚函数 善后处理。
				}while(...)
			}
		}
	}
}

三、窗口创建机制

基本框架

基础窗口类框架

  • 定义自己的框架类(CMyFrameWnd),派生自CFrameWnd;
  • 定义自己的应用程序类(CMyWinApp),派生自CWinApp,并重写父类成员虚函数InitInstance;
  • 定义全局对象CMyWinApp theApp。

相关函数

  1. 创建钩子
    HHOOK SetWindowsHookEx(
    	int idHook,		//钩子类型(WH_CBT)
    	HOOKPROC ipfn, 	//钩子处理函数
    	HINSTANCE hMod,	//应用程序实例句柄
    	SWORD DWThreadId//线程ID
    	);
    
  2. 钩子处理函数
    LRESULT CALLBACK CBTProc(
    	int nCode,		//钩子码(HCBT_CREATEWND)
    	WPARAM WParam	//窗口句柄
    	LPARAM lParam	//...
    	);
    
  3. 更改窗口处理函数
    LONG_PTR SetWindowLongPtr(
    	HWND hWnd,		//窗口句柄
    	int nIndex,		//GWLP_WNDPROC
    	LONG_PTR dwNewLong	//新的窗口处理函数名(函数地址)
    	);
    

执行流程

  1. 加载菜单;
  2. 调用CWnd::CreateEx函数创建窗口:
    • 调用PreCreateWindow函数设计和注册窗口类;
    • 在函数内部调用AfxDeferRegisterClass函数,在这个函数中设计窗口类:
      • WNDCLASS wndcls;//设计窗口类
      • wndcls.lpfnWndProc = DefWindowProc;//定义窗口的处理函数
      • 调用_AfxRegisterWithIcon函数:
        • 加载图标,并调用AfxRegisterClass函数;
        • 在函数内部调用::RegisterClass win32 API函数注册窗口。
  3. 调用AfxHookWindowCreate函数:
    • 调用SetWindowsHookEx创建WH_CBT类型的钩子,钩子的处理函数是_AfxCbtFilterHook;
    • 将框架类对象地址(pFrame)保存到当前程序线程信息中。
  4. 调用CreateWindowEx函数创建窗口,马上调用钩子处理函数;
  5. 钩子处理函数_AfxCbtFilterHook:
    • 将窗口句柄和框架类对象地址建立一对一的绑定关系;
    • 使用SetWindowLong函数,将窗口处理的函数设置AfxWndProc;
  6. 当接收到消息时,进入AfxWndProc函数;
    • AfxWndProc函数根据消息的窗口句柄,查询对应框架类对象的地址(pFrame);
    • 利用框架类对象地址(pFrame)调用框架类成员虚函数WindowProc,完成消息的处理。

程序 伪代码 如下:

/*
//三个全局变量
AFX_MODULE_STATE aaa;			//执行程序模块状态相关信息
AFX_MODULE_THREAD_STATE bbb; 	//执行程序线程状态相关信息
_AFX_THREAD_STATE* ccc;			//线程状态相关信息
*/

CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, "MFCCreate")//函数内部this为pFrame
{
	//加载菜单
	CreateEx(..., NULL,...)//函数内部this为pFrame
	{
		CREATESTRUCT cs;
		....
		cs.lpszClass = NULL;//下面将更改
		...
		cs.hInstance = AfxGetInstanceHandle();

		PreCreateWindow(cs)//设计注册窗口
		{
			AfxDeferRegisterClass(...)//设计窗口
			{
				WNDCLASS wndcls;
				...
				wndcls.lpfnWndProc = DefWindowProc;//下面将更改
				...
				_AfxRegisterWithIcon(&wndcls, "AfxFrameOrView100sd"..)
				{
					&wndcls->lpszClassName = "AfxFrameOrView100sd";
					::RegisterClass(&wndcls)
				}
			}
			cs.lpszClass = _afxWndFrameOrView; //"AfxFrameOrView100sd"
		}
		
		AfxHookWindowCreate(pFrame)
		{
			//获取全局变量&ccc(当前程序线程信息)
			_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
			//利用Win32的API函数,埋下一个类型为WH_CBT的钩子
		    ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,...);
			//将自己new的框架类对象pFrame保存到全局变量ccc的一个成员中
		    pThreadState->m_pWndInit = pFrame;
		}
		::CreateWindowEx(...);//此函数一旦执行成功,立即转到钩子处理函数。
	}
}

//钩子处理函数
_AfxCbtFilterHook(...wParam...)
{
	_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();//获取&ccc
	CWnd* pWndInit = pThreadState->m_pWndInit;//获取pFrame===pWndInit
	HWND hWnd = (HWND)wParam;//刚刚创建成功的框架窗口句柄
	pWndInit->Attach(hWnd)	//函数内部this为pFrame,参数为窗口句柄
	{
		CHandleMap* pMap = afxMapHWND(TRUE)
		{
			//获取&bbb
			AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
			//new了一个映射类对象,并将对象地址保存到bbb的一个成员中
			pState->m_pmapHWND = new CHandleMap(..);
			return pState->m_pmapHWND;//返回映射类对象地址
		}
		pMap->SetPermanent(m_hWnd = hWnd, pFrame)//函数内部this为pMap
		{
			m_permanentMap[hWnd] = pFrame;
		}
	}
	//将窗口处理函数更改为AfxWndProc(才是真正的窗口处理函数)
	(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,AfxWndProc);
}

//以WM_CREATE消息为例,捎带想着点WM_PAINT消息
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd)
	{
		CHandleMap* pMap = afxMapHWND()
		{
			AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
			return pState->m_pmapHWND;//返回就是之前保存在bbb中的映射类对象地址
		}
		pWnd = pMap->LookupPermanent(hWnd)//函数内部this为pMap
		{
			return m_permanentMap[hWnd];//返回的为pFrame
		}
	}
	AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam)//参数pWnd===pFrame
	{
		pWnd->WindowProc(nMsg, wParam, lParam);//回到自己的代码
	}
}

四、消息映射机制

作用

在不重写WindowProc虚函数的前提下,处理消息。

条件

  1. 类内必须添加声明宏:
    • DECLARE_MESSAGE_MAP()
  2. 类外必须添加实现宏:
    • BEGIN_MESSAGE_MAP(theClass, baseClass)
    • END_MESSAGE_MAP()
  3. 当一个类具备上述两个条件,这个类就可以按照消息映射机制处理消息。

程序

实例程序如下:

#include <afxwin.h>

class CMyFrameWnd : public CFrameWnd{
	DECLARE_MESSAGE_MAP()
	/*
	//宏展开
	protected: 
		static const AFX_MSGMAP* PASCAL GetThisMessageMap(); 
		virtual const AFX_MSGMAP* GetMessageMap() const;
	*/
public:
	LRESULT OnCreate( WPARAM wParam, LPARAM lParam );
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_MESSAGE( WM_CREATE, OnCreate )
END_MESSAGE_MAP()
/* 
//宏展开
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const 
{ 
	return GetThisMessageMap(); 
} 
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap() 
{ 				   
	static const AFX_MSGMAP_ENTRY _messageEntries[] =  
	{
		{ WM_CREATE, 0, 0, 0, AfxSig_lwl, (AFX_PMSG)(AFX_PMSGW) 
			(static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > (&OnCreate)) },

		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 
	}; 
	static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] }; 
	return &messageMap; 
}
*/							  
  
LRESULT CMyFrameWnd::OnCreate( WPARAM wParam, LPARAM lParam ){
	AfxMessageBox( "WM_CREATE" );
	return 0;
}

class CMyWinApp : public CWinApp{
public:
	virtual BOOL InitInstance( );
};

CMyWinApp theApp;

BOOL CMyWinApp::InitInstance( )
{
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	pFrame->Create(NULL, "MFCCreate");
	m_pMainWnd = pFrame;
	pFrame->ShowWindow( SW_SHOW );
	pFrame->UpdateWindow( );
	
	return TRUE;
}

宏展开

//DECLARE_MESSAGE_MAP()
protected: 
	static const AFX_MSGMAP* PASCAL GetThisMessageMap(); 
	virtual const AFX_MSGMAP* GetMessageMap() const;

//BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const 
{ 
	return GetThisMessageMap(); 
} 
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap() 
{ 				   
	static const AFX_MSGMAP_ENTRY _messageEntries[] =  
	{
		...

//END_MESSAGE_MAP()
		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 
	}; 
	static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] }; 
	return &messageMap; 
}
  1. GetThisMessageMap() 静态函数:
    • 定义静态变量和静态数组,并返回本类静态变量的地址(获取链表头)。
  2. _messageEntries[] 静态数组(进程级生命周期):
    • 静态数组元素结构体:
      struct AFX_MSGMAP_ENTRY	//静态数组每个元素的类型
      {
      	UINT nMessage;	//消息ID
      	UINT nCode;		//通知码
      	UINT nID;		//命令ID
      	UINT nLastID;	//最后一个命令ID
      	UINT_PTR nSig;	//处理消息的函数类型
      	AFX_PMSG pgn;	//处理消息的函数名(地址)
      }
      
    • 数组每个元素,保存的为消息ID和处理消息的函数名(地址)。
  3. messageMap 静态变量(进程级生命周期):
    • 静态变量结构体:
      struct AFX_MSGMAP	//静态变量的类型
      {
      	const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();//父类宏展开的静态变量地址
      	const AFX_MSGMAP_ENTRY* lpEntries;//本类宏展开的静态数组首地址
      }
      
    • 第一个成员,保存父类宏展开的静态变量地址(负责连接链表);
    • 第二个成员,保存本类的静态数组首地址。
  4. GetMessageMap() 虚函数:
    • 返回本类静态变量地址(获取链表头)

执行流程

  1. 消息产生进入窗口处理函数(AfxWndProc);
  2. 根据已知窗口句柄,找到和它绑定在一起的框架类对象地址(pFrame);
  3. 利用框架类对象地址(pFrame)调用框架类成员虚函数WindowProc;
  4. 获取本类对应的静态变量,并到对应数组中匹配查找;
  5. 若没有找到,则获取父类对应的静态变量,并到对应的数组中匹配查找;
  6. 若找到了,调用找到的数组元素的最后一个成员(处理消息函数地址),完成消息处理。

程序 伪代码 如下:

//以WM_CREATE消息为例,捎带想着点WM_PAINT,WM_COMMAND(到了CWnd::OnWndMsg函数路线不一样)
AfxWndProc(...)
{
	//获取和hWnd绑定在一起的框架类对象地址(pFrame===pWnd)
	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
			 
	AfxCallWndProc(pWnd...)//参数pWnd===pFrame
	{ 
		pWnd->WindowProc(...)//函数内部this为pFrame===pWnd *************
		{
			OnWndMsg(...)//函数内部this为pFrame===pWnd
			{
				//获取本类宏站开的静态变量的地址(链表头结点)
				const AFX_MSGMAP* pMessageMap = GetMessageMap();
							   
				const AFX_MSGMAP_ENTRY* lpEntry;

				for (; pMessageMap->pfnGetBaseMap != NULL;
					pMessageMap = (*pMessageMap->pfnGetBaseMap)())//遍历链表
				{
				    lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,message, 0, 0));
								   //如果找到返回找到的数组元素的地址,如果没找到返回NULL
				    if(lpEntry != NULL )
				    {
						goto LDispatch;
				    }
				}
				LDispatch:
					lpEntry->pfn; //CMyFrameWnd::OnCreate
					//调用CMyFrameWnd::OnCreate函数完成消息的处理
			}
		}
	}
}

五、菜单与工具栏

消息分类

  1. 标准的windows消息:ON_WM_XXX:
    • ON_WM_CREATE()
    • ON_WM_PAINT()
    • ON_WM_MOUSEMOVE
  2. 自定义消息:ON_MESSAGE
    • ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
  3. 命令消息:ON_COMMAND
    • ON_COMMAND(菜单项ID, 处理消息的函数名)
    • 默认处理顺序:视图类->文档类->框架类->应用程序类
    • 注:该消息处理路线与其它两个不一致

实例程序如下:

#include <afxwin.h>
#define WM_MYMESSAGE WM_USER+1001

class CMyFrameWnd : public CFrameWnd{
	DECLARE_MESSAGE_MAP()
public:
	int OnCreate( LPCREATESTRUCT pcs);
	void OnPaint( );
	void OnMouseMove( UINT nKey, CPoint pt );
	LRESULT OnMyMessage( WPARAM wParam, LPARAM lParam );
	
	int m_x;
	int m_y;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
	ON_WM_CREATE()
	ON_WM_PAINT()
	ON_WM_MOUSEMOVE()
	ON_MESSAGE( WM_MYMESSAGE,  OnMyMessage)
END_MESSAGE_MAP()

LRESULT CMyFrameWnd::OnMyMessage( WPARAM wParam, LPARAM lParam ){
	CString str;
	str.Format( "wParam=%d,lParam=%d", wParam, lParam );
	AfxMessageBox( str );
	return 0;
}
void CMyFrameWnd::OnMouseMove( UINT nKey, CPoint pt){
	m_x = pt.x;
	m_y = pt.y;
	::InvalidateRect( this->m_hWnd, NULL, TRUE );
}
void CMyFrameWnd::OnPaint( ){
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint( this->m_hWnd, &ps );
	::TextOut( hdc, m_x, m_y, "hello", 5);
	::EndPaint( m_hWnd, &ps );
}
int CMyFrameWnd::OnCreate( LPCREATESTRUCT pcs ){
	AfxMessageBox("WM_CREATE消息被处理");
	::PostMessage( this->m_hWnd, WM_MYMESSAGE, 1, 2 );
	return CFrameWnd::OnCreate( pcs );
}
class CMyWinApp : public CWinApp{
public:
	virtual BOOL InitInstance( );
};
BOOL CMyWinApp::InitInstance( ){
		CMyFrameWnd* pFrame = new CMyFrameWnd;
		pFrame->Create(NULL, "MFCCreate");
		m_pMainWnd = pFrame;
		pFrame->ShowWindow( SW_SHOW );
		pFrame->UpdateWindow( );
		return TRUE;
}
CMyWinApp theApp;

菜单

1.菜单相关问题
  • Win32:以 HMENU 菜单句柄代表菜单;
  • MFC:以CMenu类代表菜单;
2.CMenu类简介
  • 该类封装了关于菜单的各种操作成员函数,另外还封装了一个非常重要的成员变量m_hMenu(菜单句柄)。
3.菜单的使用
  • 添加菜单资源
  • 将菜单设置到窗口
    • 利用pFrame调用Create函数时,传参;
    • 在处理框架窗口的WM_CREATE消息时加载菜单:
      CMenu menu
      menu.LoadMenu(...)
      
4.设置菜单项状态
  • 点击菜单
    • Win32:
      • WM_INITMENUPOPUP
      • ::CheckMenuItem / ::EnableMenuItem
    • MFC:
      • ON_WM_INITMENUPOPUP
      • CMenu::CheckMenuItem / CMenu::EnableMenuItem
  • 右键菜单
    • Win32:
      • WM_CONTEXTMENU
      • ::TrackPopupMenu
    • MFC:
      • ON_WM_CONTEXTMENU
      • CMenu::TrackPopupMenu

实例程序如下:

#include <afxwin.h>
#include "resource.h"

class CMyFrameWnd : public CFrameWnd
{	
	DECLARE_MESSAGE_MAP( )
public:
	afx_msg void OnNew( );	//测试COMMAND消息处理顺序
	afx_msg int OnCreate( LPCREATESTRUCT pcs );
	afx_msg void OnInitMenuPopup( CMenu *pPopup, UINT nPos, BOOL i );
	afx_msg void OnContextMenu( CWnd* pWnd, CPoint pt );
public:
	CMenu menu;
};
BEGIN_MESSAGE_MAP( CMyFrameWnd, CFrameWnd )
	ON_COMMAND( ID_NEW, OnNew )
	ON_WM_CREATE( )
	ON_WM_INITMENUPOPUP()
	ON_WM_CONTEXTMENU( )
END_MESSAGE_MAP( )

void CMyFrameWnd::OnContextMenu( CWnd* pWnd, CPoint pt )
{
	//Win32
	/*
	HMENU hPopup = ::GetSubMenu(menu.m_hMenu,0);
	::TrackPopupMenu( hPopup, TPM_LEFTALIGN|TPM_TOPALIGN, pt.x, pt.y,
												0, this->m_hWnd, NULL );
	*/
	
	//MFC
	CMenu* pPopup = menu.GetSubMenu(0);
	pPopup->TrackPopupMenu( TPM_LEFTALIGN|TPM_TOPALIGN, pt.x, pt.y, this );
}
void CMyFrameWnd::OnInitMenuPopup( CMenu* pPopup, UINT nPos, BOOL i)
{
	//MFC
	//pPopup->CheckMenuItem( ID_NEW, MF_CHECKED );//内部调用Win32API函数
	
	//Win32
	::CheckMenuItem( pPopup->m_hMenu, ID_NEW, MF_CHECKED );
}
void CMyFrameWnd::OnNew( )
{
	AfxMessageBox( "框架类处理了新建菜单项被点击" );
}
int CMyFrameWnd::OnCreate( LPCREATESTRUCT pcs )
{
	menu.LoadMenu( IDR_MENU1 );
	//this->SetMenu( &menu );//内部调用Win32API函数
	::SetMenu( this->m_hWnd, menu.m_hMenu);
	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( "应用程序类处理了新建被点击" );
}
BOOL CMyWinApp::InitInstance( )
{
		CMyFrameWnd* pFrame = new CMyFrameWnd;
		pFrame->Create(NULL, "MFCCreate");
		m_pMainWnd = pFrame;
		pFrame->ShowWindow( SW_SHOW );
		pFrame->UpdateWindow( );
		return TRUE;
}
CMyWinApp theApp;

工具栏

  1. 工具栏相关类
    • CToolBarCtrl:父类为CWnd类,封装了关于工具栏控件的各种操作;
    • CToolBar:父类为CControlBar,封装了关于工具栏的操作,以及和框架窗口的关系。
  2. 工具栏的使用
    • 添加工具栏资源
    • 创建工具栏:CToolBar::CreateEx
    • 加载工具栏:CToolBar::LoadToolBar
    • 设置工具栏的停靠:
      • CToolBar::EnableDocking,设置工具栏可以停靠的位置;
      • CFrameWnd::EnableDocking,框架窗口允许工具栏停靠的位置;
      • CFrameWnd::DockControlBar,工具栏初始临时停放的位置。

实例程序如下:

#include <afxwin.h>
#include "resource.h"
#include <afxext.h>

class CMyFrameWnd : public CFrameWnd
{
	DECLARE_MESSAGE_MAP( )
public:
	afx_msg void OnNew( );
	afx_msg void OnSet( );
	afx_msg int OnCreate( LPCREATESTRUCT pcs );
public:
	CToolBar toolbar;
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd )
	ON_COMMAND( ID_SET, OnSet)
	ON_COMMAND( ID_NEW, OnNew )
	ON_WM_CREATE()
END_MESSAGE_MAP()

void CMyFrameWnd::OnSet( )
{
	AfxMessageBox( "绿色工具按钮被点击" );
}
int CMyFrameWnd::OnCreate( LPCREATESTRUCT pcs )
{
	toolbar.CreateEx( this, TBSTYLE_FLAT, WS_CHILD|WS_VISIBLE|CBRS_ALIGN_TOP|CBRS_GRIPPER);
	toolbar.LoadToolBar( IDR_TOOLBAR1 );
	toolbar.EnableDocking( CBRS_ALIGN_ANY  );
	this->EnableDocking( CBRS_ALIGN_ANY );
	this->DockControlBar( &toolbar, AFX_IDW_DOCKBAR_BOTTOM );
	return CFrameWnd::OnCreate( pcs );
}
void CMyFrameWnd::OnNew()
{
	AfxMessageBox( "新建菜单项被点击" );
}

class CMyWinApp : public CWinApp{
public:
	virtual BOOL InitInstance( );
};
BOOL CMyWinApp::InitInstance(){
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	pFrame->Create( NULL, "MFCToolBar", WS_OVERLAPPEDWINDOW, CFrameWnd::rectDefault,
									NULL, (CHAR*)IDR_MENU1);
	m_pMainWnd = pFrame;
	pFrame->ShowWindow( SW_SHOW );
	pFrame->UpdateWindow( );
	return TRUE;
}
CMyWinApp theApp;

六、运行时类信息机制

作用

在程序运行过程中可以获知对象的类的相关信息,如对象是否属于某个类。

条件

  1. 类必须派生自CObject;
  2. 类内必须添加声明宏:DECLARE_DYNAMIC( theClass );
  3. 类外必须添加实现宏:IMPLEMENT_DYNAMIC( theClass, baseClass);
  4. 当一个类具备上述三个要件后,CObject::IsKindOf函数就可以正确判断对象是否属于某个类。

程序

实例程序如下:

#include <iostream>
using namespace std;

class CAnimal : public CObject{
	DECLARE_DYNAMIC( CAnimal )
};
IMPLEMENT_DYNAMIC( CAnimal, CObject )

class CDog : public CAnimal{
	DECLARE_DYNAMIC( CDog )
/* public: 
	static const CRuntimeClass classCDog; 
	virtual CRuntimeClass* GetRuntimeClass() const;  */
};
IMPLEMENT_DYNAMIC( CDog, CAnimal )
IMPLEMENT_RUNTIMECLASS(CDog, CAnimal, 0xFFFF, NULL, NULL)
/* AFX_COMDAT const CRuntimeClass CDog::classCDog = { 
		"CDog", 
		sizeof(class CDog), 
		0xFFFF, 
		NULL, 
		RUNTIME_CLASS(CAnimal), 
		//((CRuntimeClass*)(&CAnimal::classCAnimal)),
		NULL, 
		NULL
}; 
CRuntimeClass* CDog::GetRuntimeClass() const 
{ 
	return RUNTIME_CLASS(CDog); 
	//return ((CRuntimeClass*)(&CDog::classCDog));
} */

int main(){
	CDog yellowdog;
	if( yellowdog.IsKindOf( RUNTIME_CLASS(CWnd) ) ){
		cout << "yellowdog is CWnd" << endl;
	}else{
		cout << "yellowdog isnot CWnd" << endl;
	}
	return 0;
}

宏展开

//DECLARE_DYNAMIC(CDog)
public:
	static const CRuntimeClass classCDog;
	virtual CRuntimeClass* GetRuntimeClass() const;

//IMPLEMENT_DYNAMIC(CDog, CAnimal)
AFX_COMDAT const CRuntimeClass CDog::classCDog = {
	"CDog",
	sizeof(class CDog),
	0xFFFF,
	NULL,
	RUNTIME_CLASS(CAnimal),
	//((CRuntimeClass*)(&CAnimal::classCAnimal)),
	NULL,
	NULL
};

//IMPLEMENT_RUNTIMECLASS(CDog, CAnimal, 0xFFFF, NULL, NULL)
CRuntimeClass* CDog::GetRuntimeClass() const
{
	return RUNTIME_CLASS(CDog);
	//return ((CRuntimeClass*)(&CDog::classCDog));
}
  1. classCDog 静态变量:
    • 静态变量结构体:
      struct CRuntimeClass	//宏展开的静态变量的类型
      {
      	LPCSTR m_lpszClassName;	//类名称
      	int m_nObjectSize;		//类大小
      	UINT m_wSchema;			//类版本:0xFFFF
      	CObject* (PASCAL* m_pfnCreateObject)();//动态创建机制使用,此处为NULL
      	CRuntimeClass* m_pBaseClass;//父类宏展开静态变量的地址
      	CRuntimeClass* m_pNextClass;//不使用,为NULL
      	const AFX_CLASSINIT* m_pClassInit;//不使用,为NULL
      }
      
    • 保存类名和类大小等信息,以及父类静态变量地址(负责连接链表)
  2. GetRuntimeClass() 虚函数:
    • 获取本类静态变量地址(获取链表头结点)。
  3. 宏展开各部分作用见下图:
    宏展开各部分作用

执行流程

  1. 利用对象(yellowdog)的地址调用宏展开的虚函数GetRuntimeClass(),获取本类静态变量的地址(链表头);
  2. 利用本类静态变量的地址(链表头)和目标进行比对;
  3. 若相同,则对象属于该类;
  4. 若不相同,则获取链表的下一个结点(父类静态变量的地址),循环比对。

程序 伪代码 如下:

yellowdog.IsKindOf( RUNTIME_CLASS(CDog))//函数内部this为&yellowdog,参数为链表头
{
	//利用&yellowdog调用宏展开虚函数,获取链表头结点
	CRuntimeClass* pClassThis = GetRuntimeClass();
	
	//函数内部this为链表头,参数也为链表头
	return pClassThis->IsDerivedFrom(RUNTIME_CLASS(CDog))	
	{
		const CRuntimeClass* pClassThis = this;//获取链表头
		while (pClassThis != NULL)
		{
			if (pClassThis == RUNTIME_CLASS(CDog))
				return TRUE;
			
			pClassThis = pClassThis->m_pBaseClass;
		}
		return FALSE;
	}
}

七、动态创建机制

作用

不知道类名 的情况下,将类的对象创建出来。
注:实际是通过基础代码向后兼容创建类对象。

条件

  1. 类必须派生自CObject;
  2. 类内必须添加声明宏:DECLARE_DYNCREATE( theClass );
  3. 类外必须添加实现宏:IMPLEMENT_DYNCREATE( theClass, baseClass);
  4. 当一个类具备上述三个要件后,CRuntime::CreateObject(对象加工厂)函数就可以将类的对象创建出来。

程序

实例程序如下:

#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal : public CObject{
	DECLARE_DYNAMIC( CAnimal )
};
IMPLEMENT_DYNAMIC( CAnimal, CObject )
class CDog : public CAnimal{
	DECLARE_DYNCREATE( CDog )
/* public: 
	static const CRuntimeClass classCDog; 
	virtual CRuntimeClass* GetRuntimeClass() const; 
	static CObject* PASCAL CreateObject(); */
};
IMPLEMENT_DYNCREATE( CDog, CAnimal )
/* CObject* PASCAL CDog::CreateObject() 
{ 
	return new CDog; 
} 
AFX_COMDAT const CRuntimeClass CDog::classCDog = { 
		"CDog", 
		sizeof(class CDog), 
		0xFFFF, 
		CDog::CreateObject, 
		RUNTIME_CLASS(CAnimal), 
		NULL, 
		NULL 
}; 
CRuntimeClass* CDog::GetRuntimeClass() const 
{ 
	return RUNTIME_CLASS(CDog);
} */

int main(){
	CObject* pob = RUNTIME_CLASS(CDog)->CreateObject( );//对象加工厂
	if( pob ){
		cout << pob << endl;
	}else{
		cout << "对象创建失败" << endl;
	}
	return 0;
}

宏展开

//DECLARE_DYNCREATE( CDog )
public: 
	static const CRuntimeClass classCDog; 
	virtual CRuntimeClass* GetRuntimeClass() const; 
	static CObject* PASCAL CreateObject();

//IMPLEMENT_DYNCREATE( CDog, CAnimal )
CObject* PASCAL CDog::CreateObject() 
{ 
	return new CDog; 
} 
AFX_COMDAT const CRuntimeClass CDog::classCDog = { 
		"CDog", 
		sizeof(class CDog), 
		0xFFFF, 
		CDog::CreateObject, 
		RUNTIME_CLASS(CAnimal), 
		NULL, 
		NULL 
}; 
CRuntimeClass* CDog::GetRuntimeClass() const 
{ 
	return RUNTIME_CLASS(CDog);
}

与运行时类信息机制的区别

  • 多了一个静态函数
    CObject* PASCAL CDog::CreateObject() 
    { 
    	return new CDog; 
    } 
    
  • 静态变量的第四个成员不再为NULL,而为新增加的静态函数的地址。

宏展开各部分作用

执行流程

  1. 利用本类(CDog)的静态变量,调用CRuntimeClass的成员函数CreateObject(对象加工厂函数);
  2. 获取静态变量的第四个成员(新增加静态函数),并调用;
  3. 新增加的静态函数内部,完成类对象的创建,并返回对象地址。

程序 伪代码 如下:

RUNTIME_CLASS(CDog)->CreateObject()//函数内部this为本类(CDog)的静态变量地址(链表头)
{
	CObject* pObject = (*m_pfnCreateObject)() //CDog::CreateObject
	{
		return new CDog; 
	}
	return pObject;
}

八、视图和文档

视图

作用

CView的父类为CWnd类,该类封装了关于视图窗口的各种操作,以及和文档类的数据交互操作,提供了一个用于显示数据的窗口。

使用

  1. 定义一个自己的视图类(CMyView),派生自CView,并重写父类成员纯虚函数OnDraw;
  2. 应用程序类与框架类同上;
  3. 在处理框架窗口的WM_CREATE消息时,定义CMyView类对象,并调用Create函数创建视图窗口,视图窗口的ID为:AFX_IDW_PANE_FIRST。
  4. 视图窗口类对象关系图如下:
    视图窗口类对象关系图
  5. 对象关系:
    m_pMainWnd = pFrame;
    m_pViewActive = pView;
    
    thApp
    	->m_pMainWnd (pFrame框架类对象地址)
    		->m_pViewActive (pView视图类对象地址)
    

程序

实例程序如下:

#include <afxwin.h>
#include "resource.h"

class CMyView : public CView{
	DECLARE_MESSAGE_MAP( )
public:
	void OnDraw(CDC* pDC);
	afx_msg void OnPaint( );
	afx_msg void OnNew( );
};
BEGIN_MESSAGE_MAP( CMyView, CView )
	ON_COMMAND( ID_NEW, OnNew )
	ON_WM_PAINT()
END_MESSAGE_MAP()

void CMyView::OnNew( ){
	AfxMessageBox( "视图类处理了WM_COMMAND消息" );
}
void CMyView::OnPaint( ){
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint( this->m_hWnd, &ps );
	::TextOut( hdc, 200, 200, "CMyView::OnPaint", strlen("CMyView::OnPaint") );
	::EndPaint( this->m_hWnd, &ps );
}
void CMyView::OnDraw( CDC* pDC ){
	pDC->TextOut( 100, 100, "CMyView::OnDraw" );
}

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

void CMyFrameWnd::OnNew( ){
	AfxMessageBox( "框架类处理了WM_COMMAND消息" );
}
int CMyFrameWnd::OnCreate( LPCREATESTRUCT pcs ){
	CMyView* pView = new CMyView;
	pView->Create( NULL, "MFCView", WS_CHILD|WS_VISIBLE|WS_BORDER, CRect(0,0,200,200), this, 
									AFX_IDW_PANE_FIRST );
	this->m_pViewActive = pView;
	return CFrameWnd::OnCreate( pcs );
}
void CMyFrameWnd::OnPaint( ){
	PAINTSTRUCT ps = { 0 };
	HDC hdc = ::BeginPaint( this->m_hWnd, &ps );
	::TextOut( hdc, 100, 100, "我是框架窗口客户区", strlen("我是框架窗口客户区"));
	::EndPaint( this->m_hWnd, &ps );
}

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;
	pFrame->Create( NULL, "MFCView", WS_OVERLAPPEDWINDOW,
					CFrameWnd::rectDefault, NULL, (CHAR*)IDR_MENU1);
	this->m_pMainWnd = pFrame;
	pFrame->ShowWindow( SW_SHOW );
	pFrame->UpdateWindow( );
	return TRUE;
}
CMyWinApp theApp;

文档

作用

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

使用

  1. 定义一个自己的文档类(CMyDoc),派生自CDocument;
  2. 应用程序类、框架类以及视图类同上;
  3. 文档类对象关系图如下:
    文档类对象关系图
  4. 对象关系:
    thApp
    	->m_pMainWnd (框架类对象地址pFrame)
    		->m_pViewActive (视图类对象地址pView)
    			->m_pDocument (文档类对象地址pDoc)
    				->m_viewList (所有视图类列表对象地址)
    

执行流程

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

程序 伪代码 如下:

CMyFrameWnd* pFrame = new CMyFrameWnd;//视图类实例
CMyDoc* pDoc = new CMyDoc;文档类实例

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

//创建框架窗口
pFrame->LoadFrame(...,&cct)
{
	Create(...,&cct)
	{
		CreateEx(...&cct)
		{
			CREATESTRUCT cs;
			....
			cs.lpCreateParams = &cct;
			::CreateWindowEx(...,&cct);//创建主框架窗口
		}
	}
}

//处理框架窗口的WM_CREATE消息
CFrameWnd::OnCreate( pcs )//函数内部this为pFrame,参数可以获取::CreateWindowEx的12个参数
{
	CCreateContext* pContext = (CCreateContext*)pcs->lpCreateParams;//获取&cct
	OnCreateHelper(pcs, pContext)//函数内部this为pFrame,pContext===&cct
	{
		OnCreateClient(pcs, pContext)//函数内部this为pFrame,pContext===&cct
		{
			CreateView(pContext..)//函数内部this为pFrame,pContext===&cct
			{
				//动态创建视图类对象,并返回对象地址
				CWnd* pView = pContext->m_pNewViewClass->CreateObject();
				pView->Create(..,pContext)//函数内部this为pView,pContext===&cct
				{
					CreateEx(..,pContext)//函数内部this为pView,pContext===&cct
					{
						CREATESTRUCT cs;
						....
						cs.lpCreateParams = pContext;//pContext===&cct
						::CreateWindowEx(...,pContext);//创建视图窗口
					}
				}
			}
		}
	}
}

//处理视图窗口的WM_CREATE消息
CView::OnCreate( pcs )//函数内部this为pView,参数可以获取::CreateWindowEx函数的12个参数
{
	CCreateContext* pContext = (CCreateContext*)lpcs->lpCreateParams;//获取&cct
	pContext->m_pCurrentDoc->AddView(pView)//函数内部this为pDoc,参数为视图类对象地址
	{
		m_viewList.AddTail(pView);//文档类对象用一个链表成员变量,保存视图类对象地址
		pView->m_pDocument = this;//视图类对象用一个普通成员变量,保存文档类对象地址
	}
}

窗口切分

相关类

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 ) //动态创建机制
	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 );
	split.CreateView( 0, 0, RUNTIME_CLASS(CMyView), CSize(100,100), pContext );
	split.CreateView( 0, 1, pContext->m_pNewViewClass, CSize(100,100), pContext );
	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

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

文档与视图关系

命令消息处理顺序

  • 默认处理顺序:视图类->文档类->框架类->应用程序类。
  • 命令消息关系图:
    命令消息关系图
  • 程序 伪代码 如下:
    //WM_COMMAND消息的路线
    OnCommand(wParam, lParam)//函数内部this为pFrame
    {
    	CWnd::OnCommand(...)//函数内部this为pFrame
    	{
    		OnCmdMsg(...)//函数内部this为pFrame   *****CFrameWnd::OnCmdMsg 起点
    		{
    			CView* pView = GetActiveView()//函数内部this为pFrame
    			{
    				return this->m_pViewActive;//活动视图窗口对象地址
    			}
    			
    			//1.
    			pView->OnCmdMsg(...) ===> 终点 CCmdTarget::OnCmdMsg 此函数内部this为pView
    			//2.
    			m_pDocument->OnCmdMsg(...) ==> 终点CCmdTarget::OnCmdMsg 此函数内部this为文档类对象
    			//3.
    			CWnd::OnCmdMsg(...) ==> 终点 CCmdTarget::OnCmdMsg 此函数内部this为pFrame
    			//4.
    			CWinApp* pApp = AfxGetApp();//获取&theApp
    			pApp->OnCmdMsg(...) ==> 终点 CCmdTarget::OnCmdMsg 此函数内部this为&theApp
    		}
    	}
    }
    

文档与视图关系

  • 视图类成员函数:获取和视图类对象关联的文档类对象,调用GetDocument()函数;
  • 文档类成员函数:当文档类数据发生变化时,调用UpDataAllViews刷新和文档类对象相关联的视图类对象。
  • 文档类与视图类对象关系图如下:
    文档类与视图类对象关系图
    • 文档类对象用一个链表成员变量保存视图类对象地址;
    • 视图类对象用一个普通成员变量保存文档类对象地址;
    • 即一个文档类对象可以对应多个视图类对象,而一个视图类对象只能对应一个文档类对象。

程序

实例程序如下:

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

九、序列化机制

作用

以二进制流形式读写硬盘文件(效率很高)。

相关类

  • CFile - 文件操作类,完成硬盘文件的读写操作;
    class CFile //封装了关于文件读写等操作。
    CFile::Open
    CFile::Write / Read
    CFile::Close
    CFile::SeekToBegin / SeekToEnd / Seek
    
  • CArchive - 归档类,完成内存数据的读写操作。
    class CArchive
    {
    	enum Mode{store = 0, load = 1...};
    	Bool m_nMode;	//访问方式
    	int m_nBufSize;	//buff的大小
    	CFile* m_pFile;	//操作的文件对象
    	BYTE* m_lpBufCur;	//当前指向
    	BYTE* m_lpBufMax;	//终止指向
    	BYTE* m_lpBufStar;	//开始指向
    	...
    }
    

使用

序列化机制的使用

  1. 创建或打开文件:CFile::Open;
  2. 定义归档类对象:CArchive ar;
  3. 数据序列化(存储/写):ar << 数据;
  4. 关闭归档类对象:ar.Close();
  5. 关闭文件:CFile::Close()。

反序列化机制的使用

  1. 创建或打开文件:CFile::Open;
  2. 定义归档类对象:CArchive ar;
  3. 数据反序列化(加载/读):ar >> 数据;
  4. 关闭归档类对象:ar.Close();
  5. 关闭文件:CFile::Close()。

实例程序

#include <afxwin.h>
#include <iostream>
using namespace std;
void Store( )//序列化(存储/写)数据
{
	CFile file;
	file.Open( "E:/.../serial.txt", CFile::modeCreate | CFile::modeWrite );
	CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区,大小4096字节
	long age = 18;
	ar << age;	//将18保存当前指向的位置,并向后移动当前指向,相应字节数。
	float score = 88.5;
	ar << score;//将88.5保存当前指向的位置,并向后移动当前指向,相应字节数。
	CString name = "zhangsan";  
	ar << name;
	ar.Close( );
	file.Close( );
}
void Load( )//反序列化(加载/读)数据
{
	CFile file;
	file.Open( "E:/.../serial.txt", CFile::modeRead );
	CArchive ar( &file, CArchive::load, 4096 );//归档类对象,维护缓冲区,大小4096字节
	long age;
	ar >> age;	//当反序列化第一个数据时候,内部将文件中所有数据读入ar维护的buff中
	float score;
	ar >> score;//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取
	CString name;
	ar >> name;	//当反序列化后续数据时候,不需要到硬盘文件中读取,直接到ar维护的buff中读取
	ar.Close( );
	file.Close( );
	cout << age << ' ' << score << ' ' << name << endl;
}
int main(){
	Store( );
	Load( );
	return 0;
}

执行过程

序列化机制的执行过程

  • ar对象维护一个缓冲区;
  • 将各个数据依次序列化(存储/写)到ar对象维护的缓冲区中,并将m_lpBufCur的指针指向移动相应字节;
  • 如果ar维护的缓冲区不足,则将ar维护的缓冲区的数据写入硬盘文件,并重置m_lpBufCur为开始指向;
  • 当关闭ar对象时,将ar对象维护的缓冲区数据写入硬盘文件,并释放ar对象维护的缓冲区。

反序列化机制的执行过程

  • ar对象维护一个缓冲区;
  • 当反序列化第一个数据时,将文件数据全部读取到ar对象维护的缓冲区中,并将第一个数据反序列化到第一个变量,然后将m_lpBufCur的指针移动相应字节;
  • 依次反序列化每个数据到变量中;
  • 当关闭ar对象时,释放ar对象维护的缓冲区。

执行过程伪代码

/* 
 * 序列化执行过程伪代码,反序列化过程类似
 */

class CArchive	//归档类
{        
	enum Mode { store = 0, load = 1};
	BOOL m_nMode;  	//访问方式
	int m_nBufSize;	//buff的大小
	CFile* m_pFile;	//操作的文件对象
	BYTE* m_lpBufCur;	//当前指向
	BYTE* m_lpBufMax;	//终止指向
	BYTE* m_lpBufStart;	//开始指向
	...
}

CFile file;
file.Open( "E:/.../serial.txt", CFile::modeCreate | CFile::modeWrite );
CArchive ar(&file, CArchive::store, 4096) => CArchive::CArchive(&file,04096)
{
	m_nMode = CArchive::store;// 0
	m_pFile = &file;	//“E:/....serial.txt”
	m_nBufSize = 4096;
	m_lpBufStart = new BYTE[m_nBufSize];
	m_lpBufCur = m_lpBufStart;
	m_lpBufMax = m_lpBufStart + 4096;
}

long age = 18;
ar << age => CArchive::operator<<(age)//函数内部this为&ar
{
	if (m_lpBufCur + sizeof(long) > m_lpBufMax) 
	{
		Flush();
	}
	*m_lpBufCur = age;//18
	m_lpBufCur += sizeof(long); 
}

float score = 88.5;
ar << score => CArchive::operator<<(score)//函数内部this为&ar
{
	if (m_lpBufCur + sizeof(float) > m_lpBufMax) 
	{
		Flush();
	}
	*m_lpBufCur = score;//88.5 
	m_lpBufCur += sizeof(float);
}

CString name = "zhangsan";  
ar << name => CArchive::operator<<(name)//函数内部this为&ar
{
	AfxWriteStringLength(ar, 8)
	{
		ar << (unsigned char)nLength;//8
	}
	
	Write(name, 8)//函数内部this为&ar
	{
		memcpy_s(m_lpBufCur, (size_t)(m_lpBufMax - m_lpBufCur), name, 8);
		m_lpBufCur += 8;
	}
}

ar.Close( )//函数内部this为&ar
{
	Flush()//函数内部this为&ar
	{
		&file->Write(m_lpBufStart, ULONG(m_lpBufCur - m_lpBufStart));
		m_lpBufCur = m_lpBufStart;//重置当前指向
	}
}

	 18  88.5  8  zhangsan
	|--------------------------------------------------------------------|
	|                     |                                              |
m_lpBufStart			m_lpBufCur 									m_lpBufMax

序列化类对象

本质

序列化类对象,就是序列化对象的各个成员变量。

使用

  • 类必须派生自CObject;
  • 类内必须添加声明宏 DECLARE_SERIAL(theClass);
  • 类外必须添加实现宏 IMPLEMENT_SERIAL(theClass, baseClass, 1);
  • 类内必须重写虚函数 Serialize();
  • 当类满足上述三个条件后,类对象即可以序列化到文件中。

实例程序

#include <afxwin.h>
#include <iostream>
using namespace std;

class CMyDoc : public CDocument{
	
	DECLARE_SERIAL( CMyDoc )//声明宏
	/* 
	_DECLARE_DYNCREATE(CMyDoc) //动态创建机制的声明宏
	AFX_API friend CArchive& AFXAPI operator>>(CArchive& ar, CMyDoc* &pOb);//操作符重载函数
	*/
	
public:
	CMyDoc(int age=0, float score=0.0, CString name="")
		:m_age(age),m_score(score),m_name(name)
		{}
	int m_age;
	float m_score;
	CString m_name;
	
	virtual void Serialize( CArchive& ar );//重载虚函数
};
IMPLEMENT_SERIAL( CMyDoc, CDocument, 1 )

/* 
_DECLARE_DYNCREATE(CMyDoc) //宏展开,见第七部分动态创建机制
CArchive& AFXAPI operator>>(CArchive& ar, CMyDoc* &pOb) 
{ 
	pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(CMyDoc)); 
			return ar; 
} */

void CMyDoc::Serialize( CArchive& ar ){
	if( ar.IsStoring() ){
 		ar << m_age << m_score << m_name;//序列化基本类型变量  
	}else{
		ar >> m_age >> m_score >> m_name;//反序列化基本类型变量
	}
}
void Store( )//序列化(存储/写)数据
{
	CFile file;
	file.Open("E:/.../serial.txt", CFile::modeCreate | CFile::modeWrite);
	CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区,大小4096字节
	CMyDoc data(18, 88.5, "zhangsan");
	ar << &data; //序列化对象,就是将对象的各个成员变量序列化
	ar.Close( );
	file.Close( );
}
void Load( )//反序列化(加载/读)数据
{
	CFile file;
	file.Open( "E:/.../serial.txt", CFile::modeRead );
	CArchive ar( &file, CArchive::load, 4096 );//归档类对象,维护缓冲区,大小4096字节
	CMyDoc* pdata = NULL;
	ar >> pdata;
	ar.Close( );
	file.Close( );
	cout << pdata->m_age << ' ' << pdata->m_score << ' ' << pdata->m_name << endl;
}
int main(){
	Store( );
	Load( );
	return 0;
}

执行过程详解

//序列化类对象过程
CFile file;
file.Open("E:/.../serial.txt", CFile::modeCreate | CFile::modeWrite);
CArchive ar(&file, CArchive::store, 4096);//归档类对象,维护缓冲区,大小4096字节
CMyDoc data(18, 88.5, "zhangsan");
ar << &data => operator << (ar, const &data)
{
	ar.WriteObject(&data)//函数内部this为&ar
	{
		CRuntimeClass* pClassRef = &data->GetRuntimeClass();//文档类静态变量
		WriteClass(pClassRef);	//将类的相关信息(类名/类大小/类版本)存入ar维护的buff中
		(&data)->Serialize(ar)	//函数内部this为&data
		{
			ar << this->m_age << this->m_score << this->m_name; //序列化基本类型变量
		}
	}
}

//反序列化类对象过程
CFile file;
file.Open( "E:/.../serial.txt", CFile::modeRead );
CArchive ar( &file, CArchive::load, 4096 );//归档类对象,维护缓冲区,大小4096字节
CMyDoc* pdata = NULL;
ar >> pdata => operator >> (ar, pdata) 
{
	pdata = ar.ReadObject(RUNTIME_CLASS(CMyDoc))//函数内部this为&ar
	{
		//从文件读取类的相关信息,和RUNTIME_CLASS(CMyDoc)中信息进行比对,
		//如果相同返回RUNTIME_CLASS(CMyDoc),如果不同返回NULL
		CRuntimeClass* pClassRef = ReadClass(RUNTIME_CLASS(CMyDoc), ...);

		//动态创建CMyDoc类的对象,并返回对象地址   
		CObject* pOb = RUNTIME_CLASS(CMyDoc)->CreateObject();

		pOb->Serialize(ar)//函数内部this为刚刚创建的CMyDoc类对象(pOb)
		{
			ar >> m_age >> m_score >> m_name;//反序列化基本类型变量
		}
		return pOb;
	}
}

十、MFC架构程序

单文档视图架构

特点

只能关联一个文档(只有一个文档类对象)。

使用

  • 参与架构的类:CFrameWnd / CWinApp / CView / CDocument;
  • 需要用到的类:CDocTemplate->CSingleDocTemplate(单文档模板类) / CDocManager(文档管理类);
  • 参与架构的四个类,除了应用程序类CWinApp 外,其余的三个类(CFrameWnd / CView / CDocument)均需要支持动态创建机制。

程序

实例程序如下:

#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, "我是视图窗口" );
}

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;

执行流程

  • 对象关系:
    theApp
    	-> m_pDocManager(文档管理类对象地址)
    		-> m_templateList(单文档模板类对象地址)
    			-> CSingleDocTemplate* pTemplate
    				-> m_pOnlyDoc		//唯一的文档类对象地址
    				-> m_pDocClass		//RUNTIME_CLASS(CMyDoc)
    				-> m_pFrameClass	//RUNTIME_CLASS(CMyFrameWnd)
    				-> m_pViewClass		//RUNTIME_CLASS(CMyView)
    
  • OnFileNew()函数的执行过程:
    1. 利用theApp获取文档管理类对象地址;
    2. 利用文档管理类对象获取单文档模板类对象地址;
    3. 利用单文档模板类对象获取RUNTIME_CLASS(CMyFrameWnd),调用CreateObject(对象加工厂函数)动态创建框架类对象;
    4. 利用单文档模板类对象获取RUNTIME_CLASS(CMyDoc),调用CreateObject(对象加工厂函数)动态创建文档类对象;
    5. 利用框架类对象调用LoadFrame函数,创建框架窗口。
  • 程序 伪代码 如下:
    OnFileNew( )//函数内部this为&theApp
    {
    	m_pDocManager->OnFileNew()//函数内部this为文档管理类对象地址
    	{
    		CDocTemplate* pTemplate = m_templateList.GetHead();//取出单文档模板类对象地址
    		pTemplate->OpenDocumentFile()//函数内部this为单文档模板类对象地址
    		{
    			OpenDocumentFile(..)//函数内部this为单文档模板类对象地址
    			{
    				pDocument = CreateNewDocument()//函数内部this为单文档模板类对象地址
    				{
    					//动态创建文档类对象,并返回对象地址
    					CDocument* pDocument = m_pDocClass->CreateObject();
    
    					AddDocument(pDocument)//函数内部this为单文档模板类对象地址
    					{
    						m_pOnlyDoc = pDocument;
    					}
    				}
    				pFrame = CreateNewFrame(pDocument..)//函数内部this为单文档模板类对象地址
    				{
    					CCreateContext context;
    					...
    					context.m_pCurrentDoc = pDocument;//文档类对象地址
    					context.m_pNewViewClass = m_pViewClass;//RUNTIME_CLASS(CMyView)
    					//动态创建框架类对象,并返回对象地址
    					CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
    
    					pFrame->LoadFrame(...., &context);//创建框架窗口
    					//后续过程见**文档 - 执行流程**的伪代码
    				}
    			}
    		}
    	}
    }
    

多文档视图架构

特点

可以管理多个文档(可以有多个文档类对象)。

使用

  • 参与架构的类:CMDIFrameWnd / CMDIChildWnd / CWinApp / CView / CDocument;
  • 需要用到的类:CDocTemplate->CMultiDocTemplate(多文档模板类) / CDocManager(文档管理类);
  • CMDIChildWnd / CView / CDocument类需要支持动态创建机制。

程序

实例程序如下:

#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, "我是视图窗口" ); 
}

class CMyChild : public CMDIChildWnd{ 
	DECLARE_DYNCREATE( CMyChild )
};
IMPLEMENT_DYNCREATE( CMyChild, CMDIChildWnd )

class CMyFrameWnd : public CMDIFrameWnd{ //自己造主框架窗口类的对象
};

class CMyWinApp : public CWinApp{//自己造应用程序类的对象
public:
	virtual BOOL InitInstance( );
};
BOOL CMyWinApp::InitInstance( ){
	CMyFrameWnd* pFrame = new CMyFrameWnd;
	pFrame->LoadFrame( IDR_MENU1 );
	m_pMainWnd = pFrame;
	pFrame->ShowWindow( SW_SHOW );
	pFrame->UpdateWindow( );

	CMultiDocTemplate* pTemplate = new CMultiDocTemplate( IDR_MENU2, 
														RUNTIME_CLASS(CMyDoc),
														RUNTIME_CLASS(CMyChild),
														RUNTIME_CLASS(CMyView));
	AddDocTemplate( pTemplate );

	OnFileNew( );//创建文档类对象,创建子框架类对象,创建子框架窗口,创建视图类对象,创建视图窗口,文档类对象和视图类对象关联
	OnFileNew( );//创建文档类对象,创建子框架类对象,创建子框架窗口,创建视图类对象,创建视图窗口,文档类对象和视图类对象关联
	OnFileNew( );//创建文档类对象,创建子框架类对象,创建子框架窗口,创建视图类对象,创建视图窗口,文档类对象和视图类对象关联

	return TRUE;
}
CMyWinApp theApp;

执行流程

  • 对象关系:
    theApp
    	-> m_pDocManager(文档管理类对象地址)
    		-> m_templateList(多文档模板类对象地址)
    			-> CMultiDocTemplate* pTemplate
    				-> m_docList		//保存多个文档类对象地址
    				-> m_pDocClass		//RUNTIME_CLASS(CMyDoc)
    				-> m_pFrameClass	//RUNTIME_CLASS(CMyFrameWnd)
    				-> m_pViewClass		//RUNTIME_CLASS(CMyView)
    
  • OnFileNew()函数的执行过程:
    1. 利用theApp获取文档管理类对象地址;
    2. 利用文档管理类对象获取多文档模板类对象地址;
    3. 利用多文档模板类对象获取RUNTIME_CLASS(CMyChild),调用CreateObject(对象加工厂函数)动态创建框架类对象;
    4. 利用多文档模板类对象获取RUNTIME_CLASS(CMyDoc),调用CreateObject(对象加工厂函数)动态创建文档类对象;
    5. 利用框架类对象调用LoadFrame函数,创建子框架窗口。
  • 程序 伪代码 如下:
    OnFileNew( )//函数内部this为&theApp
    {
    	m_pDocManager->OnFileNew()//函数内部this为文档管理类对象地址
    	{
    		CDocTemplate* pTemplate = m_templateList.GetHead();//取出多文档模板类对象地址
    		pTemplate->OpenDocumentFile()//函数内部this为多文档模板类对象地址
    		{
    			OpenDocumentFile(...)//函数内部this为多文档模板类对象地址
    			{
    				pDocument = CreateNewDocument()//函数内部this为多文档模板类对象地址
    				{
    					//动态创建文档类对象,并返回对象地址
    					CDocument* pDocument = m_pDocClass->CreateObject();
    							   
    					AddDocument(pDocument)//函数内部this为多文档模板类对象地址
    					{
    						m_docList.AddTail(pDoument);//将文档类对象扔到多文档模板类的链表成员中
    					}
    				}
    				pFrame = CreateNewFrame(pDocument..)//函数内部this为多文档模板类对象地址
    				{
    					CCreateContext context;
    					...
    					context.m_pCurrentDoc = pDocument;//文档类对象地址
    					context.m_pNewViewClass = m_pViewClass;//RUNTIME_CLASS(CMyView)
    					//动态创建框子架类对象,并返回对象地址
    					CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();
    
    					pFrame->LoadFrame(...., &context);//创建子框架窗口
    					//后续过程见**文档 - 执行流程**的伪代码
    				}
    			}
    		}
    	}
    }
    

对话框架构

分类

MFC中对话框分为模态对话框与非模态对话框两种。
注:模态对话框为假模态对话框。

使用

参与架构的类:CDialog / CWinApp;

  1. 添加对话框资源;
  2. 定义一个自己的对话框类(CMyDlg),管理对话框资源,派生自CDialog或CDialogEx均可;
  3. 其余详见相关程序。

WIN32对话框

间接方式创建非模态对话框
  • 添加对话框资源;
  • 查找资源:FindResource(…);
  • 加载资源:LoadResource(…);
  • 锁定资源:LockResource(…);
  • 创建非模态对话框:CreateDialogIndirect(…)。
销毁非模态对话框

销毁非模态对话框,使用DestroyWindow函数,不能使用EndDialog函数,因为EndDialog函数只是将非模态对话框隐藏,而没有销毁。

实例程序
#include <windows.h>
#include "resource.h"

HINSTANCE g_hInstance = 0;
INT CALLBACK DlgProc( HWND hwndlg, UINT msgID, WPARAM wParam, LPARAM lParam )
{
	switch( msgID )
	{
	case WM_DESTROY:
		MessageBox( NULL, "I'm Killed", "Infor", MB_OK );
		break;
	case WM_SYSCOMMAND:
		if( wParam == SC_CLOSE ){
			HWND hWnd = GetParent( hwndlg );//获取对话框窗口的父窗口句柄
			EnableWindow( hWnd ,TRUE );		//销毁窗口重新启用父窗口
			DestroyWindow( hwndlg );		//销毁非模态对话框, 不能使用EndDialog
//			EndDialog( hwndlg, 1001 );		//只能隐藏非模态对对话框, 但可以销毁模态对话框
		}
		break;
	}
	return FALSE;//对话框的消息交给真正对话框窗口处理函数处理。
}

void OnNoModel( HWND hWnd )
{
	EnableWindow( hWnd, FALSE );//创建子窗口禁用父窗口,呈现模态对话框假象。
	//直接创建非模态对话框
	//HWND hDlg = CreateDialog( g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, DlgProc );
	
	//间接创建非模态对话框(CreateDialog函数内部执行过程)
	HRSRC hRs = FindResource( g_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), RT_DIALOG );	//查找资源
	HGLOBAL hGl = LoadResource( g_hInstance, hRs );										//加载资源
	LPCDLGTEMPLATE pTemplate = (LPCDLGTEMPLATE)LockResource( hGl );						//锁定资源
	HWND hDlg = CreateDialogIndirect( g_hInstance, pTemplate, hWnd, DlgProc );			//创建非模态对话框

	ShowWindow( hDlg, SW_SHOW );
}

void OnCommand( HWND hWnd, WPARAM wParam )
{
	switch(LOWORD(wParam))
	{
	case ID_NOMODEL:
		OnNoModel( hWnd );
		break;
	}
}

//窗口处理函数( 自定义,处理消息)
LRESULT CALLBACK WndProc( HWND hWnd, UINT msgID, WPARAM wParam, LPARAM lParam )
{
	switch(msgID)
	{
	case WM_COMMAND:
		OnCommand( hWnd, wParam );
		break;
	case WM_DESTROY:
		PostQuitMessage( 0 );
		break;
	}
	return DefWindowProc( hWnd, msgID, wParam, lParam );
}

//入口函数
int CALLBACK WinMain(HINSTANCE hIns, HINSTANCE hPreIns, LPSTR lpCmdLine, int nCmdShow)
{
	g_hInstance = hIns;
	//注册窗口类
	WNDCLASS wc = { 0 };
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	wc.hCursor = NULL;
	wc.hIcon = NULL;
	wc.hInstance = hIns;
	wc.lpfnWndProc = WndProc;
	wc.lpszClassName = "Main";
	wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);//(CHAR*)IDR_MENU1;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	RegisterClass( &wc );//将以上所有赋值全部写入操作系统。
	//在内存创建窗口
	HWND hWnd = CreateWindowEx( 0, "Main", "window", WS_OVERLAPPEDWINDOW,
								100, 100, 500, 500, NULL, NULL, hIns, NULL );
	//显示窗口
	ShowWindow( hWnd, SW_SHOW );
	UpdateWindow( hWnd );
	//消息循环
	MSG nMsg = { 0 };
	while( GetMessage(&nMsg,NULL,0,0) )
	{
		TranslateMessage( &nMsg );
		DispatchMessage( &nMsg );//将消息交给窗口处理函数来处理。
	}
	return 0;
}

非模态对话框

实例程序
#include <afxwin.h>
#include "resource.h"

class CMyDlg : public CDialog
{
	DECLARE_MESSAGE_MAP()
public:
	void OnZjwOK();
	void OnZjwCancel( );
};
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
	ON_COMMAND( IDOK, OnZjwOK )
	ON_COMMAND( IDCANCEL, OnZjwCancel )
END_MESSAGE_MAP()

void CMyDlg::OnZjwCancel( )
{
//	CDialog::OnCancel( );
	this->DestroyWindow( );
}
void CMyDlg::OnZjwOK()
{
//	CDialog::OnOK();//只能将无模式对话框隐藏,并没有销毁。
	::DestroyWindow( this->m_hWnd );
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance( );
};
BOOL CMyWinApp::InitInstance( )
{
	CMyDlg* pdlg = new CMyDlg;
	pdlg->Create( IDD_DIALOG1 );
	m_pMainWnd = pdlg;
	pdlg->ShowWindow( SW_SHOW );
	return TRUE;
}
CMyWinApp theApp;

执行过程
  • 程序启动进入入口函数WinMain;
  • 获取应用程序类对象theApp的地址;
  • 利用theApp地址调用InitApplication,初始化当前应用程序的数据;
  • 利用theApp地址调用InitInstance函数,在函数中创建非模态对话框并显示;
  • 利用theApp地址调用CWinApp的Run函数进行消息循环:
    • 如果没有消息,利用theApp地址调用OnIdle虚函数实现空闲处理;
    • 当对话框销毁(调用DestroyWindow),消息循环退出;
    • 退出前利用theApp地址调用ExitInstance虚函数实现退出后的处理工作。
  • 程序伪代码如下:
    AFX_MODULE_STATE aaa;		//当前程序模块状态信息
    AFX_MODULE_THREAD_STATE bbb;//当前程序线程状态信息
    
    CWinApp::CWinApp()//构造全局对象CMyWinApp theApp
    {
    	//获取全局变量&aaa
    	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
    	//获取全局变量&bbb
    	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
    	//将&theApp保存到bbb的一个成员中  
    	pThreadState->m_pCurrentWinThread = this;
    
    	AfxGetThread()
    	{
    		AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
    		CWinThread* pThread = pState->m_pCurrentWinThread;
    		return pThread;//返回的为&theApp
    	}
    	pModuleState->m_pCurrentWinApp = this;//将&theApp保存到aaa的一个成员中
    	AfxGetApp()
    	{
    		return AfxGetModuleState()->m_pCurrentWinApp;//返回&theApp
    	}
    }
    //进入入口函数
    WinMain(...)
    {
    	AfxWinMain(..)
    	{
    		CWinThread* pThread = AfxGetThread();
    		CWinApp* pApp = AfxGetApp();//获取&theApp
    		pApp->InitApplication();	//利用theApp调用应用程序类的成员 虚函数(初始化)
    		pThread->InitInstance()		//利用theApp调用应用程序类的成员 虚函数(创建并显示无模式对话框)
    		{
    			CMyDlg* pdlg = new CMyDlg;
    			pdlg->Create( IDD_DIALOG1 )//函数内部this为pdlg(自己new的对话框类对象地址)
    			{
    				CDialog::Create(MAKEINTRESOURCE(IDD_DIALOG1), ..)//函数内部this为pdlg
    				{
    					//以下两行代码,查找并加载对话框资源
    					HRSRC hResource = ::FindResource(hInst, lpszTemplateName, RT_DIALOG);
    					HGLOBAL hTemplate = LoadResource(hInst, hResource);
    
    					CreateIndirect(MAKEINTRESOURCE(IDD_DIALOG1), ...)
    					{
    						LPCDLGTEMPLATE lpDialogTemplate = LockResource(hDialogTemplate);
    						CreateIndirect(lpDialogTemplate..)
    						{
    							CreateDlgIndirect(...)
    							{
    								::CreateDialogIndirect(...);//以间接方式创建无模式对话框
    							}
    						}
    					}
    				}
    			}
    		}
    		pThread->Run()//函数内部this为&theApp
    		{
    			CWinThread::Run()//函数内部this为&theApp
    			{
    				for (;;)
    				{
    					while(没有消息时)
    					{
    						OnIdle(..);//空闲处理(虚函数)
    					}
    					do
    					{
    						if(::GetMessage抓到WM_QUIT消息)
    						return ExitInstance(..);//善后处理(虚函数) 
    					}while(...)
    				}
    			}
    		}
    	}
    }
    //点击OK发出WM_COMMAND消息
    CDialog::OnOK()
    {
    	EndDialog(IDOK)
    	{
    		::EndDialog(m_hWnd, nResult);//只能将无模式对话框隐藏
    	}
    }
    

模态对话框

实例程序
#include <afxwin.h>
#include "resource.h"

class CMyDlg : public CDialog
{
public:
	DECLARE_MESSAGE_MAP()
	enum{IDD=IDD_DIALOG1};
public:
	afx_msg void OnOK();
	CMyDlg(): CDialog(IDD){}
};
BEGIN_MESSAGE_MAP( CMyDlg, CDialog )
	ON_COMMAND(IDOK, OnOK)
END_MESSAGE_MAP()
void CMyDlg::OnOK()
{
	CDialog::OnOK();
}

class CMyWinApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
BOOL CMyWinApp::InitInstance()
{
	CMyDlg dlg;	//栈空间
	m_pMainWnd = &dlg;
	int nRet = dlg.DoModal();
	return FALSE;//不再执行MFC库中安排的Run函数
}
CMyWinApp theApp;
执行过程
  • 程序启动进入入口函数WinMain;
  • 获取应用程序类对象theApp的地址;
  • 利用theApp地址调用InitApplication,初始化当前应用程序的数据;
  • 利用theApp地址调用InitInstance函数,在函数中调用DoModal函数;
    • DoModal内部执行过程:
    • 将父窗口设置为不可用;
    • 创建非模态对话框;
    • 进入消息循环;
    • 退出消息循环(父类的OnOK / OnCancel导致循环退出);
    • 将父窗口设置为可用状态;
    • 销毁非模态对话框。
  • 不再执行CWinApp的Run函数进行的消息循环。
  • 程序伪代码如下:
    AFX_MODULE_STATE aaa;		//当前程序模块状态信息
    AFX_MODULE_THREAD_STATE bbb;//当前程序线程状态信息
    
    CWinApp::CWinApp()//构造全局对象CMyWinApp theApp
    {
    	//获取全局变量&aaa
    	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
    	//获取全局变量&bbb
    	AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
    	//将&theApp保存到bbb的一个成员中
    	pThreadState->m_pCurrentWinThread = this;
    
    	AfxGetThread()
    	{
    		AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
    		CWinThread* pThread = pState->m_pCurrentWinThread;
    		return pThread;//返回的为&theApp
    	}
    	pModuleState->m_pCurrentWinApp = this;//将&theApp保存到aaa的一个成员中
    	AfxGetApp()
    	{
    		return AfxGetModuleState()->m_pCurrentWinApp;//返回&theApp
    	}
    }
    
    //进入入口函数
    WinMain(...)
    {
    	AfxWinMain(..)
    	{
    		CWinThread* pThread = AfxGetThread();
    		CWinApp* pApp = AfxGetApp();//获取&theApp
    		pApp->InitApplication();	//利用theApp调用应用程序类的成员 虚函数(初始化)
    		pThread->InitInstance()
    		{
    			CMyDlg dlg===CDialog(IDD)//函数内部this为&dlg
    			{
    				m_lpszTemplateName=MAKEINTRESOURCE(IDD)//将对话框资源ID保存dlg的一个成员中
    			}
    			m_pMainWnd = &dlg;
    			dlg.DoModal()//函数内部this为&dlg
    			{
    				//以下三行代码,查找加载并锁定对话框资源
    				HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
    				hDialogTemplate = LoadResource(hInst, hResource);
    				lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);
    				
    				HWND hWndParent = PreModal();//获取父窗口的句柄
    				::EnableWindow(hWndParent, FALSE);//将父窗口设置为不可用状态
    				CreateDlgIndirect(...);//间接方式创建非模态对话框
    
    				RunModalLoop(...)//函数内部this为&dlg
    				{
    					for (;;)  //消息循环
    					{
    						while(没有消息){ 空闲处理 }
    						do{
    							//消息循环的相关函数;
    							if (!ContinueModal())//函数内部this为&dlg(m_nFlags(24/8) & 0x0010)
    							goto ExitModal;
    						}while(....);
    					}
    					ExitModal:
    					return m_nModalResult;
    				}
    			}
    			::EnableWindow(hWndParent, TRUE);//将父窗口设置为可用
    			DestroyWindow();//销毁非模态对话框
    			return m_nModalResult;//dlg的一个成员变量
    		}
    	}
    }
    
    //父类处理点击OK按钮发出的WM_COMMAND消息
    CDialog::OnOK()//函数内部this为&dlg
    {
    	EndDialog(IDOK) //参数为1
    	{
    		EndModalLoop(IDOK)//函数内部this为&dlg
    		{
    			m_nModalResult = IDOK; // 1
    			m_nFlags = 8;
    		}
    		::EndDialog(m_hWnd, nResult);//只能将无模式对话框隐藏
    	}
    }
    //父类处理点击Cancel按钮发出的WM_COMMAND消息
    CDialog::OnCancel()//函数内部this为&dlg
    {
    	EndDialog(IDCANCEL) //参数为2
    	{
    		EndModalLoop(IDCANCEL)//函数内部this为&dlg
    		{
    			m_nModalResult = IDCANCEL; // 2
    			m_nFlags = 8;
    		}
    		::EndDialog(m_hWnd, nResult);//只能将无模式对话框隐藏
    	}
    }
    

十一、绘图

Window下绘图需要使用绘图设备,在WIN32中使用绘图设备句柄 (HDC),在MFC中使用类对象完成相关操作。

相关类

  • CDC类(绘图设备类):封装了各种绘图相关的函数,以及两个非常重要的成员变量m_hDC和m_hAttrubDC
    • CPaintDC类,封装了在WM_PAINT消息中绘图的绘图设备;
    • CClientDC类,封装了在客户区绘图的绘图设备。
  • CGDIObject类(绘图对象类):封装了各种绘图对象相关的操作,以及一个非常重要的成员变量m_hObject(绘图对象句柄)
    • CPen类,封装了画笔的相关操作;
    • CBrush类,封装了画刷的相关操作;
    • CFont类,封装了字体的相关操作;
    • CBitmap类,封装了位图的相关操作。

实例程序

//CPaintDC 
void CMFCDrawView::OnPaint()
{
	CPaintDC dc(this); //::BeginPaint
	dc.Rectangle( 100, 100, 300, 300 );	//绘制矩形
//	::Rectangle( dc.m_hDC, 100, 100, 300, 300 );
}
//CClientDC 
void CMFCDrawView::OnClient()
{
	CClientDC dc(this);
	dc.Ellipse( 300, 300, 500, 500 );	//绘制椭圆
}
//CPen 
void CMFCDrawView::OnPen()
{
	CClientDC dc( this );
	CPen pen(PS_SOLID, 2, RGB(255,0,0));

	CPen* oldpen = dc.SelectObject( &pen );
//	HGDIOBJ nOldPen = ::SelectObject( dc.m_hDC, pen.m_hObject );

	dc.Rectangle( 100, 100, 300, 300 );
//	::Rectangle( dc.m_hDC, 100, 100, 300, 300 );

	dc.SelectObject( oldpen );
//	::SelectObject( dc.m_hDC, nOldPen );

	pen.DeleteObject( );
//	::DeleteObject( pen.m_hObject );
}
//CBrush 
void CMFCDrawView::OnBrush()
{
	CClientDC dc(this);
	CBrush brush(RGB(0,255,0));
	CBrush* oldbrush = dc.SelectObject( &brush );
	dc.Rectangle( 100, 100, 300, 300 );
	dc.SelectObject( oldbrush );
	brush.DeleteObject( );
}
//CFont 
void CMFCDrawView::OnFont()
{
	CClientDC dc( this );
	CFont font;
	font.CreatePointFont(300, "黑体");	//::CreateFont(..............)
	CFont* oldfont = dc.SelectObject( &font );
	dc.TextOut( 100, 100, "hello");
	dc.SelectObject( oldfont );
	font.DeleteObject();
}
//CBitmap 
void CMFCDrawView::OnBmp()
{
	//添加位图资源(操作资源无需写代码)

	//创建一个和当前DC,相匹配的内存DC
	CClientDC dc( this );
	CDC memdc;
	memdc.CreateCompatibleDC( &dc ); //::CreateCompatibleDC
	
	//将位图的数据送给内存DC
	CBitmap bmp;
	bmp.LoadBitmap( IDB_BITMAP1 ); //::LoadBitmap
	CBitmap* oldbmp = memdc.SelectObject( &bmp );//::SelectObject
	//成像
	dc.BitBlt( 100, 100, 48, 48, &memdc, 0, 0, SRCCOPY );//::BitBlt
	memdc.SelectObject( oldbmp );//::SelectObject
	//销毁位图
	bmp.DeleteObject( );//::DeleteObject
	//销毁内存DC
	memdc.DeleteDC( );//::DeleteDC
}

部分执行过程详解

CPaintDC dc(pView) => CPaintDC::CPaintDC(...//函数内部this为&dc
{
	Attach(::BeginPaint(pView->m_hWnd, &m_ps)//函数内部this为&dc
	{
		m_hDC = hDC;//将BeginPaint获取的绘图设备句柄,保存到dc对象的一个成员变量中
		SetAttribDC(m_hDC)//函数内部this为&dc
		{
			m_hAttribDC = m_hDC;//dc对象的另一个成员变量也保存了绘图设备句柄
		}
	}
}

CPen pen(PS_SOLID, 2, RGB(255,0,0)) => CPen::CPen(PS_SOLID, 2, RGB(255,0,0))//函数内部this &pen
{
	Attach(::CreatePen(PS_SOLID, 2, RGB(255,0,0))//函数内部this &pen
	{
		m_hObject = hObject;//将::CreatePen获取的画笔句柄,保存到pen对象的一个成员变量中
	}
}

十二、对象和控件

对象与控件绑定

将控件窗口与类对象绑定具有两个作用:

  • 与数据类对象绑定,对象和控件可以进行数据交换;
  • 与控件类对象绑定,对象可以代表整个控件。

与数据类型对象绑定

  • 数据类型对象和控件可以实现数据交互;
  • 重写父类成员虚函数DoDataExchange(),在函数内部通过一系列的DDX_xxx函数,实现控件和数据类型对象的数据交互;
  • 实现数据交互,需调用UpdateData()函数:
    • UpdateData(TRUE):控件 -> 变量
    • UpdateData(FALSE):变量 -> 控件

与控件类型对象绑定

  • 控件类型对象和控件绑定,控件类型对象就代表了整个控件;
  • 重写父类成员虚函数DoDataExchange(),在函数内部通过一系列的DDX_xxx函数,实现控件句柄和控件类型对象的绑定;
  • 通过控件类型对象控制该控件。

控件消息的处理

WM_COMMAND消息中参数:

  • LOWORD(wParam) - 菜单项ID,控件ID
  • HIWORD(wParam) - 菜单项:为0;控件:通知码
  • IParam - 均无用

基本控件的使用

  • 控件如何与数据类型对象绑定;
  • 控件如何与控件类型对象绑定;
  • 控件的消息如何处理。
    理解了以上三点,则掌握了控件的使用方法,具体控件的使用方法,见官方帮助文档。
  • 9
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不敲代码de小猿同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值