C++ MFC/BCG编程(二):文档/视图体系结构、应用程序框架分析


文档/视图体系结构

MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据。

MFC在类CDocumentCView中为文档和视图提供了基础结构。CWinAppCFrameWnd和其他类与CDocumentCView合作,把所有的片段连在了一起。

文档/视图应用程序从应用程序框架结构中得到了最大的好处。几乎任何依赖某类文档的程序都能用文档/视图生成。文档/视图体系结构极大地简化了打印和打印预览、向磁盘中存储文档以及读取文档的过程。

别被术语“文档”误导,认为文档/视图体系结构对编写字处理器和电子表格程序有用。文档仅仅是程序数据的抽象表示。文档既可能是保存计算机象棋游戏中棋盘位置的字节数组,也可能是一个电子表格。

MFC程序的分类

  • MFC控制台程序:可以调用MFC的控制台程序
  • MFC库程序
    • MFC静态库
    • MFC动态库
    • MFC扩展库:扩展库只能被MFC应用程序调用
  • MFC窗口程序
    • 单文档视图架构程序(SDI)
    • 多文档视图架构程序(MDI)
    • 对话框应用程序

在这里插入图片描述

MFC应用程序的构成

在Visual Studio中,如果已经以Debug方式编译链接过程序,则会在解决方案文件夹下和工程子文件夹下各有一个名为“Debug”的文件夹,而如果是Release方式编译则会有名为“Release”的文件夹。这两种编译方式将产生两种不同版本的可执行文件:

  • Debug版本
    可执行文件中包含了用于调试的信息和代码。
  • Release版本
    没有调试信息,不能进行调试,但可执行文件较小。

创建Hello World工程,工程中文件结构:

  • 解决方案相关文件
    包括解决方案文件夹下的.sdf文件、.sln文件、.suo文件和ipch文件夹。

    • .sdf文件和ipch目录一般与智能提示、错误提示、代码恢复和团队本地仓库有关。
    • .sln文件和.suo文件为MFC自动生成的解决方案文件,它包含当前解决方案中的工程信息,存储解决方案的设置。
  • 工程相关文件
    包括工程文件夹下的.vcxproj文件和.vcxproj.filters文件。

    • .vcxproj文件是MFC生成的工程文件,它包含当前工程的设置和工程所包含的文件等信息。
    • .vcxproj.filters文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录结构信息。
  • 资源文件
    一般使用MFC生成窗口程序都会有对话框、图标、菜单等资源,应用程序向导会生成资源相关文件:res目录、HelloWorld.rc文件和Resource.h文件。

    • res目录:工程文件夹下的res目录中含有应用程序默认图标、工具栏使用图标等图标文件。
    • HelloWorld.rc:包含默认菜单定义、字符串表,制定了默认的About对话框和应用程序默认图标文件等。
    • resource.h:含有各种资源的ID定义。
  • 预编译头文件
    几乎所有MFC程序的文件都要包含afxwin.h等文件(外部依赖项),如果每次都编译一次则会大大减慢编译速度。所以把常用的MFC头文件都放到了stdafx.h中,然后由stdafx.cpp包含stdafx.h文件,编译器对stdafx.cpp只编译一次,并生成编译后的预编译头HelloWorld.pch(Precomplied Header Flie),大大提高了编译效率。
    "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx.h"指令,使用projectname.pch编译这条指令之后的所有代码。

    因此,所有MFC程序cpp实现文件的第一条语句都是#include "stdafx.h"

  • 编译链接生成的文件
    工程文件夹下的Debug和Release子文件夹中包含了编译链接时产生的中间文件。
    解决方案文件夹下的Debug和Release子文件夹中主要包含有应用程序的可执行文件。

  • 应用程序头文件和源文件
    应用程序向导(MFC Application Wizard)会根据应用程序的类型(单文档、多文档或基于对话框的程序)自动生成一些头文件和源文件,这些文件是工程的主体部分,用于实现主框架、文档、视图等。

文件用途
stdafx.h标准Afx头文件
stdafx.cpp#include "stdafx.h"用来产生预编译的类型信息
resource.h定义各种资源ID
HelloWorld.h定义了从CWinApp类派生的应用程序对象HelloWorldApp
HelloWorld.cpp定义了HelloWorldApp的实现,并定义HelloWorldApp类型的全局变量theApp
MainFrm.h定义了从CFrameWnd派生的框架窗口对象MainFrame
MainFrm.cppMainFrame类的实现
HelloWorldDoc.h定义了从CDocument派生的文档对象 HelloWorldDoc
HelloWorldDoc.cppHelloWorldDoc类的实现
HelloWorldView.h定义了从CView派生的视图对象 HelloWorldView
HelloWorldView.cppHelloWorldView类的实现
  • HelloWorldApp的实现用到了所有的用户定义对象,包含了其他类的定义;
  • HelloWorldView的实现用到了HelloWorldDoc;
  • 其他对象的实现只涉及自己的定义。

MFC应用程序框架主要类之间的关系

  • HelloWorldApp类处理消息,将收到的消息分发给相应的对象。
  • MainFrame类是视图类HelloWorldView的父窗口,HelloWorldView就显示在MainFrame的客户区中。
  • 视图类HelloWorldView用来显示文档类HelloWorldDoc中的数据,并根据对视图类的操作修改文档类的数据。一个视图类只能跟一个文档类相联系,而一个文档类可以跟多个视图类相联系。

参与架构的类:

//应用程序类:负责程序流程
class HelloWorldApp : public CWinApp
//单文档SDI主框架窗口类
class MainFrame : public CFrameWnd
//视图窗口类:显示数据
class HelloWorldView : public CView
//文档类:数据管理
class HelloWorldDoc : public CDocument
//如果是多文档MDI工程
//多文档主框架窗口类
class MainFrame : public CMDIFrameWnd

//多文档子框架窗口类
class ChildFrame : public CMDIChildWnd

SDK应用程序运行流程

只使用Windows API,不使用MFC创建一个HelloWorld工程,即SDK应用程序。
SDK应用程序的入口函数为WinMain函数。

#include <windows.h>    
  
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);   
     
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)      
{      
	const static TCHAR appName[] = TEXT("Hello world");      
	WNDCLASSEX myWin;      
	myWin.cbSize = sizeof(myWin);      
	myWin.style = CS_HREDRAW | CS_VREDRAW;      
	myWin.lpfnWndProc = myWndProc;      
	myWin.cbClsExtra = 0;      
	myWin.cbWndExtra = 0;      
	myWin.hInstance = hInstance;      
	myWin.hIcon = 0;      
	myWin.hIconSm  = 0;      
	myWin.hCursor = 0;      
	myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);      
	myWin.lpszMenuName = 0;      
	myWin.lpszClassName = appName;      
	//Register      
  	if (!RegisterClassEx(&myWin)) return 0;      
  	const HWND hWindow = CreateWindow(      
    	appName,      
    	appName,      
    	WS_OVERLAPPEDWINDOW,      
    	CW_USEDEFAULT,      
    	CW_USEDEFAULT,      
    	CW_USEDEFAULT,      
   		CW_USEDEFAULT,      
    	0,      
    	0,      
    	hInstance,      
    	0);      
  	ShowWindow(hWindow,iCmdShow);      
  	UpdateWindow(hWindow);      
  	{      
    	MSG msg;      
    	while(GetMessage(&msg,0,0,0))      
    	{      
      	TranslateMessage(&msg);      
      	DispatchMessage(&msg);      
    	}      
    	return (int)msg.wParam;      
  	}      
}      
     
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)      
{      
  	if (msg==WM_PAINT)      
  	{      
    	PAINTSTRUCT ps;      
    	const HDC hDC = BeginPaint(hWindow,&ps);      
    	RECT rect;      
    	GetClientRect(hWindow,&rect);      
    	DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);      
    	EndPaint(hWindow,&ps);      
    	return 0;      
  	}      
  	else if (msg==WM_DESTROY)      
  	{      
    	PostQuitMessage(0);      
    	return 0;      
  	}      
  	return DefWindowProc(hWindow,msg,wParam,lParam);      
}  

程序运行流程

  1. 进入WinMain函数
  2. 初始化WNDCLASSEX,调用RegisterClassEx函数注册窗口类
  3. 调用ShowWindowUpdateWindow函数显示并更新窗口
  4. 进入消息循环

关于消息循环

Windows应用程序是消息驱动的,系统或用户让应用程序进行某项操作或完成某个任务时会发送消息,进入程序的消息队列,然后消息循环会将消息队列中的消息取出,交予相应的窗口过程处理。

此程序的窗口过程函数就是myWndProc函数,窗口过程函数处理完消息就完成了某项操作或任务。本例是要显示“HELLO WORLD”字符串,UpdateWindow函数会发送WM_PAINT消息,但是此消息不经过消息队列而是直接送到窗口过程处理,在窗口过程函数中最终绘制了“HELLO WORLD”字符串。

MFC应用程序运行流程

下面是MFC应用程序的运行流程,通过MFC库中代码进行分析:

MFC应用程序的核心就是基于CWinApp类的应用程序对象,CWinApp提供了消息循环来检索消息并将消息调度给应用程序的窗口。
一个MFC应用程序可以有且仅有一个应用程序对象,对象必须声明为在全局范围内有效(也就是全局对象),以便它在程序开始时即在内存中被实例化。

1.定义全局对象theApp,构造函数进入应用程序入口

extern HelloWorldApp theApp;

2.实例化,并创建显示一个窗口
成员函数:InitInstance()
功能:初始化应用程序实例和窗口实例,
虚函数CWinApp::InitInstance必须在派生类中重写。在InitInstance函数中,编写初始化代码

BOOL HelloWorldApp::InitInstance()
{
	//......略
	CWinApp::InitInstance();
	
	// 注册应用程序的文档模板。  文档模板将用作文档、框架窗口和视图之间的连接
	CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CHelloWorldDoc),
		RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
		RUNTIME_CLASS(CHelloWorldView));
	if (!pDocTemplate)
		return FALSE;
	AddDocTemplate(pDocTemplate);

	// 分析标准 shell 命令、DDE、打开文件操作的命令行
	CCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);

	// 调度在命令行中指定的命令。  如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
	if (!ProcessShellCommand(cmdInfo))
		return FALSE;

	// 唯一的一个窗口已初始化,因此显示它并对其进行更新
	m_pMainWnd->ShowWindow(SW_SHOW);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}

InitInstance中的ProcessShellCommand函数又调用了MainFrameLoadFrame函数注册并创建了窗口,执行完ProcessShellCommand函数以后,调用了m_pMainWndShowWindowUpdateWindow函数显示并更新框架窗口。这些与上面的SDK程序十分类似

class MainFrame : public CFrameWnd

MainFrame为应用程序提供一个窗口,同时实现消息处理功能。OnCreate()创建窗体,将之赋于CFrameWnd对象上。

3.进入消息循环
了,上面的AfxWinMain函数中调用了pThreadRun函数(位于THRDCORE.cpp中),在Run中包含了消息循环。Run函数的代码如下:

int CWinThread::Run()       
{       
	//.............略       
	// phase2: pump messages while available
	do
	{       
		// pump message, but quit on WM_QUIT
		if (!PumpMessage())
			return ExitInstance();       
      
		// reset "no idle" state after pumping "normal" message       
		if (IsIdleMessage(&m_msgCur))       
		{       
			bIdle = TRUE;       
      
			lIdleCount = 0;       
      
		}       
	} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));       
	//..............略       
}       
        
BOOL CWinThread::PumpMessage()       
{     
	return AfxInternalPumpMessage();    
}    
  
BOOL AFXAPI AfxInternalPumpMessage()   
{   
	_AFX_THREAD_STATE *pState = AfxGetThreadState();   
      
	if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))          
	{       
		//.............略       
	}       
		//...............略       
	if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))   
	{   
		::TranslateMessage(&(pState->m_msgCur));   
		::DispatchMessage(&(pState->m_msgCur));   
	}    
	return TRUE;       
}  

可以看到PumpMessage中通过调用GetMessageTranslateMessageDispatchMessage等建立了消息循环并投递消息。

窗口过程函数AfxWinProc形式如下:

LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)   
{   
	……   
	CWnd*pWnd=CWnd::FromHandlePermanent(hWnd);  
	Return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);   
}

SDK应用程序和MFC应用程序对比:
运行流程是类似的,都是先进行初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消息都由窗口过程函数处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值