在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;
}
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中进行过预定义,一般键盘消息、鼠标消息以及和窗口消息都是标准消息。