windows编程之MFC

    在Windows编程中MFC无法绕过去的一个话题,一直以来MFC都是饱受争议的产物,但是不可否认,MFC确实在某些开发中带给编程人员很大的便利,时至今日,微软产品几次变更,MFC依然保留在Virtual Studio中,而且他也没有发生很大的变化,侯俊杰在书中说这款Application Framework是具有革命精神的,给开发人员带来不一样的局面。我们还是来谈谈MFC编程的来龙去脉吧。

    MFC初始化的过程

    在创建一个名为Test的单文档的应用时,Virtual Studio已经为我们创建了几个类,这几个类分别是CAboutDlg,CMainFrame,CTestApp,CTestDoc,CTestView这几个类,首先需要关注的是这几个类的继承关系,

    CObject--->CCmdTarget-->CWnd-->CDialog-->CAboutDlg;

    CObject-->CCmdTarget-->CWnd-->CFrameWnd-->CMainFrame;

    CObject--> CCmdTarget-->CWinThread-->CWinApp-->CTestApp;

    CObject-->CCmdTarget-->CDocument-->CTestDoc;

    CObject-->CCmdTarget-->CWnd-->CView-->CCtrlView-->CEditView-->CTestView;

    从这个继承关系中,我们可以看到上面创建的类都是从CObject和CCmdTarget类继承而来,了解这个关系图,对我们了解MFC初始化过程有着非常重要的作用,我们再来说一说以上5个类的作用,CAboutDlg这个是关于一个About对话框的类,CMainFrame是主应用框架,CTestApp就是我们这个应用,CTestDoc是处理数据的类,CTestView是展现数据的类;用概括的话说就是,CTestApp是应用程序的本体,CMainFrame是我们看到的本体的外观,CTestDoc是处理数据的,CTestView是展现数据的,CAboutDlg是一个子窗口,而CTestView是粘贴在CMainFrame上的客户区,他没有覆盖的部分就是非客户区,隶属于CMainFrame。了解了以上的关系,我们就知道在那个类里处理什么样的消息和命令了。

    在MFC初始化过程中,应该是首先拥有本体,其次是本体的外观,而外观又是用来操作数据的。所以MFC对此的处理过程是这样的,

    1、一切都从CTest2App theApp;这个全局变量开始,于是我们开始初始化的曲折历程,接着就是CTestApp的基类的构造函数,依次类推;

    2、接着程序就到了_tWinMain,也就是主函数的入口了,至于是怎么到达的,侯俊杰的书中说是连接器直接加到应用代码中的;

    3、_tWinMain调用AfxWinMain,其内容请到VC安装目录中的VC98->MFC->SRC中的WINMAIN.CPP中查看,其主要代码如下,

    

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine, int nCmdShow)
{
	int nReturnCode = -1;
	CWinThread* pThread = AfxGetThread();
	CWinApp* pApp = AfxGetApp();

	AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow);	
	pApp->InitApplication();
	pThread->InitInstance();
	nReturnCode = pThread->Run();

	AfxWinTerm();
	return nReturnCode;
}

    4、从代码中可以看到熟悉的InitApplication,InitInstance,Run这几个方法,从C++的多态性可以知道这几个方法实际的调用方法分别是,

     CWinApp::InitApplication(); CTestApp::InitInstance();CWinApp::Run();

    5、AfxWinInit()做了一些初始化的工作,具体代码可参看VC98->MFC->SRC中APPINIT.CPP代码;

    6、CWinApp::InitApplication()中主要是对CDocManager的操作,这个操作非常重要,可参见侯俊杰《深入简出MFC》347页第八章的说明;

    7、接着道CTestApp::InitInstance()方法,其中定义了一个CSingleDocTemplate* pDocTemplate;指针变量,并将CTest2Doc、CMainFrame、CTest2View关联起来;

    8、接着到了ProcessShellCommand(cmdInfo);我们深入这个方法再探个究进,这个方法再APPUI2.CPP中,这里面有Switch,case组成的判断语句,而判断条件就是传经来的参数rCmdInfo.m_nShellCommand,我们从工具提供的查看变量可知,这个变量值是FileNew,接着会发起AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL)这样一个命令,我们可以在CTestApp中找个这个命令,ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew);

    9、程序调用APPDLG.CPP中的CWinApp::OnFileNew()方法,接着调用m_pDocManager->OnFileNew();这个方法再DOCMGR.CPP中,接着调用pTemplate->OpenDocumentFile(NULL);这是一个虚函数,我们可以知道他实际上是调用CSingleDocTemplate::OpenDocumentFile();这个方法再DOCSINGL.CPP中,接下来调用的是

pDocument = CreateNewDocument();和pFrame = CreateNewFrame(pDocument, NULL);这两个方法不是虚函数,所以调用的是CDocTemplate::CreateNewDocument();

和CDocTemplate::CreateNewFrame();

   10、创建了Document和Frame,接着pFrame->LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, &context);

   11、这个方法再WINFRM.CPP中接着调用CFrameWnd::Create();接着是CreateEx();CFrame没有实现这个方法,所以调用基类的,CWnd::CreateEx()方法,

BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
	LPCTSTR lpszWindowName, DWORD dwStyle,
	int x, int y, int nWidth, int nHeight,
	HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
	// allow modification of several common create parameters
	CREATESTRUCT cs;
	cs.dwExStyle = dwExStyle;
	cs.lpszClass = lpszClassName;
	cs.lpszName = lpszWindowName;
	cs.style = dwStyle;
	cs.x = x;
	cs.y = y;
	cs.cx = nWidth;
	cs.cy = nHeight;
	cs.hwndParent = hWndParent;
	cs.hMenu = nIDorHMenu;
	cs.hInstance = AfxGetInstanceHandle();
	cs.lpCreateParams = lpParam;

	if (!PreCreateWindow(cs))
	{
		PostNcDestroy();
		return FALSE;
	}

	AfxHookWindowCreate(this);
	HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
			cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
			cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);


	ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
	return TRUE;
}

    在这里我们终于见到了熟悉的代码,::CreateWindowEx()这是系统库函数,MSDN中有下面这样的一句话,

  The CreateWindowEx function sends WM_NCCREATE, WM_NCCALCSIZE, and WM_CREATE messages to the window being created. 
    接收WM_CREATE消息的是CMainFrame类,CMainFrame::OnCreate();方法,接着里面调用了CFrameWnd::OnCreate(lpCreateStruct)方法,调用CFrameWnd::OnCreate()方法,接着是CFrameWnd::OnCreateHelper()方法,接着是OnCreateClient(lpcs, pContext)方法,接着CreateView()方法,在这里面也就动态创建了View对象。

    12、回到CTestApp::InitInstance()方法,接着是 m_pMainWnd->ShowWindow(SW_SHOW);m_pMainWnd->UpdateWindow();在调用m_pMainWnd->UpdateWindow()

方法时,系统会发出WM_PAINT消息,CMainFrame和CTestView可以处理这个消息。先调用CMainFrame后调用CTestView中的OnPaint()方法。

    13、接着是pThread->Run(),可以去THRDCORE.CPP中去查看代码。至此MFC窗口创建过程算是结束了。


MFC消息的处理过程

虽然MFC的消息处理也是颇为曲折,但是还是有一定的路线图的,我们按图索骥就可以知道MFC处理消息的机制。

1、MFC将消息分为三大类,命令消息WM_COMMAND,标准消息(除WM_COMMAND外,任何以WM_开头的消息均为标准消息),Control Notification(可以理解为控件通知);

2、凡自CWnd派生类,可以处理任何Windows消息,与窗口无关的MFC类(例CDocument和CWinApp)如果也想处理消息,必须派生自CCmdTarget,并且只可能收到WM_COMMAND命令消息。(语自侯俊杰《深入浅出MFC》第九章)。

3、一般Windows消息就直接在消息映射表中上溯,寻找其归宿,如果是通知消息(Notification)就交给OnNotify处理,如果是WM_COMMAND消息就交给OnCommand处理。

4、如果消息从Frame窗口开始,那么其首先去View类中寻找,其次去Frame本身寻找,最后去CWinApp中寻找;

5、消息从View中开始,那么就先在View本身寻找,接着寻找Document,Document会去Document TempLate中寻找。

      6、标准消息都有默认的处理函数,在CWnd中进行过预定义,一般键盘消息、鼠标消息以及和窗口消息都是标准消息。

你可能感兴趣的:(windows)