一、CWinApp——取代WinMain 的地位
CWinApp的派生对象被称为application object,可以想见,CWinApp本身就代表一个程序本体。CWinApp声明包含在AFXWIN.H文件中,传统上SDK程序的WinMain所完成的工作现在由CWinApp的三个函数完成:
virtual BOOL InitApplicaton();
virtual BOOL InitInstance();
virtual int Run();
记录主窗口handle的成员变量m_pMainWnd也包含在CWinThread中了(CWinApp的父类,MFC2.5包含在CWinApp中)
CWinApp的定义在AFXWIN.H中
二、CFrameWnd——取代WndProc的地位
传统的SDK窗口函数写法是:
MFC程序有新的做法,我们在Hello程序也为CMyFrameWnd准备了两个消息处理程序,声明如下
OnPaint处理WM_PAINT,后者处理WM_COMMAND的IDM_ABOUT。
引爆——Application object
下图中的theApp就是Hello程序的application object,每一个MFC应用程序都有一个而且也只有这么一个全局对象。
父类CWinApp的构造函数在APPCORE.CPP文件中
theApp配置完成后,WinMain登场。MFC早已准备好并由链接器直接加到应用程序代码中的,如下:_tWinMain函数的“_t”是为了支持Unicode而准备的一个宏。
此外,在DLLMODUL.CPP中有一个DLLMain函数。
AfxWinMain在WinMain.cpp中定义可见Windows程序进入点,程序代码可从MFC的WINMAIN.CPP中获得。
其中,AfxGetApp是一个全局函数,定义于AFXWIN1.INL中
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
{return afxCurrentWinApp;}
而afxCurrentWinApp 有定义于AFXWIN.H中:
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp
再根据稍早所诉CWinApp::CWinApp中的操作,我们于是知道,AfxGetApp其实就是取得CMyWinApp对象指针。所以,AfxWinMain中这样的操作:
CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();
导致调用:
CWinApp::InitApplication();//因为CMyWinApp并没有改写InitApplication.
CMyWinApp::InitInstance();//因为CMyWinApp改写了InitInstance
CWinApp::Run();//因为CMyWinApp并没有改写Run
InitApplication应该是注册窗口类的场所,InitInstance应该是产生窗口并显示窗口的场所,Run应该是获得消息并分派消息的场所。
下面分别讨论AfxWinMain的四个主要操作以及引发的行为。
AfxWinInit——AFX内部初始化操作
AfxWinInit是继CWinApp构造函数之后的第一个操作。(定义在APPINIT.CPP)
其中调用的AfxInitThread函数的操作摘要如下(在THRDCORE.CPP中定义)
MFC注册四个窗口类,但不再是AfxWinInit中完成。
CWinApp::InitApplication
因为InitApplication是CWinApp的一个虚拟函数;而在CMyWinApp中并没有改写它,所以pApp->InitApplication()调用的是CWinApp::InitApplication。 此函数在APPCORE.CPP中定义
CMyWinApp::InitInstance
因为InitInstance在CMyWinApp被定义所以pApp->InitInstance();调用CMyWinApp::InitInstance
注意:应用程序一定要改写虚函数InitInstance,因为他在CWinApp中只是个空函数,没有任何内建(默认)操作。
CFrameWin::Create产生主窗口:(并先注册窗口类)
CMyWinApp::InitInstance一开始new了一个CMyFrameWnd对象,准备用作主框架窗口的C++对象。new会引发构造函数:
CMyFrameWnd::CMyFrameWnd
{
Create(NULL,"Hello MFC",WS_OVERLAPPEDWINDOW,rectDefault,NULL,"MainMenu");
}
其中Create 是CFrameWnd的成员函数,它将产生一个窗口。但,使用哪一个窗口类呢?(这里所谓的“窗口类”是由RegisterClass所注册的一份数据结构,不是C++类)。根据CFrameWnd::Create 的规格:
BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle = WS_OVERLAPPEDWINDOW,
const RECT& rect = rectDefault,
CWnd* pParentWnd = NULL,
LPCTSTR lpszMenuName =NULL,
DWORD dwExStyle =0,
CCreateContext* pContext =NULL);
八个参数中的后六个参数都有默认值,只有前两个参数必须指定。
第一个参数lpszClassName指定WNDCLASS窗口类,我们放置NULL代表的意思是要以MFC内建的窗口类产生一个标准的外框窗口。但,此时此刻Hello程序根本不存在任何窗口,Create函数在产生窗口之前会引发窗口类的注册操作,
第二个参数lpszWindowName指定窗口标题。
第三个参数dwStyle指定窗口风格,默认是WS_OVERLAPPEDWINDOW,也正是最常用的一种,他被定义为(在WINDWS.H之中):
#define WS_OVERLAPPEDWINDOW(WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX)
因此,如果你不想要窗口右上角的极大极小钮,就得这么做:
Create(NULL,"Hello MFC",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME,rectDefault,NULL,"MainMenu");
如果希望窗口有垂直滚动条,就得在第三个参数上再增加WS_VSCROLL风格。除了上述标准的另有扩充风格,在Create的第七个参数dwExStyle指定之。扩充风格唯有以::CreateWindowEx(而非::CreateWindow)函数才能完成。其实CFrameWnd::Create最终调用的正是:CreateWindowEx.
Windows3.1提供五种窗口扩充风格:
WS_EX_DLGMODALFRAME、WS_EX_NOPARENTNOTIFY、WS_EX_TOPMOST、WS_EX_ACCEPTFILES、WS_EX_TRANSPARENT
Windows9x有更多选择,包括WS_EX_WINDOWEDGE和WS_EX_CLIENTEDGE,让窗口更具3D立体感。
Create的第四个参数 rect指定窗口的位置与大小。默认值rectDefault是CFrameWnd的一个static成员变量,
可以为Create(NULL,"Hello MFC",WS_OVERLAPPEDWINDOW,CRect(40,60,240,460),NULL,"MainMenu");//起始位置(40,60),宽200,高400
第五个参数pParentWnd指定父窗口。对于一个top-level窗口而言,此值应为NULL,表示没有父窗口(其实是有的,父窗口就是desktop窗口)
第六个参数lpszMenuName指定菜单。本例使用一份在RC中准备好的菜单"MainMenu"。
第八个参数pContext是一个指向CCreateContext结构的指针,framework利用它,在具备Document/View结构的程序中初始化外窗口(第8章)本例不具备Document/View结构默认值为NULL.
具体的Create实现如下在WINFRM.CPP中
在函数中调用CreateEx.注意,CWnd有成员函数CreateEx,但其派生类CFrameWnd并没有改变虚函数CreateEx,所以这里虽然调用的是CFrameWnd::CreateExx,其实是从父类继承下来的CWnd::CreateEx。
函数中调用的PreCreateWindow是虚函数,在CWnd和CFrameWnd中都有定义。由于this指针所指对象的缘故,这里应该调用的是CFrameWnd::PreCreateWindow(还记得第2章的虚函数的那种行为方式)
WINFRM.CPP
其中AfxDeferRegisterClass是一个AFXIMPL.H中的宏
出现在上述函数中的六个窗口类卷标代码,分别定义与AFXIMPL.H中:
#define AFX_WND_REG (0x0001)
#define AFX_WNDCONTROLBAR_REG (0x0002)
#define AFX_WNDMDIFRAME_REG (0x0004)
#define AFX_WNDFRAMEORVIEW_REG (0x0008)
#define AFX_WNDCOMMCTLS_REG (0x0010)
#define AFX_WNDOLECONTROL_REG (0x0020)
出现在上述函数中的五个窗口类名称,分别定义与WINCORE.CPP中:
const TCHAR _afxWnd[] =AFX_WND;
const TCHAR _afxWndControlBar[] =AFX_WNDCONTROLBAR;
const TCHAR _afxWndMDIFrame[] =AFX_WNDMDIFRAME;
const TCHAR _afxWndFrameOrView[] =AFX_WNDFRAMEORVIEW;
const TCHAR _afxWndOleControl[] =AFX_WNDOLECONTROL;
等号右边的那些AFX_常数又定义于AFXIMPL.H中:
AfxEndDeferRegisterClass的操作。它调用两个函数完成实际的窗口类注册操作,一个是RegisterWithIcon,一个是AfxRegisterClass:
注意,不同类的PreCreateWindow成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类。如果我们指定类是NULL,那么就使用系统默认类。从CWnd及其各个派生类的PreCreateWindow成员函数可以看出,整个Framework针对不同功能的窗口使用了那些窗口类:
指定图标和光标形状实为RegisterClass责任而非CreateWindow的责任!
MFC开放了一个窗口,可以在HELLO.RC中这样设定图标:
AFX_IDI_STDFRAME ICON DISCARDABLE "HELLO.ICO"
你可以从AfxEndDeferRegisterClass的第55行看出,当它调用RegisterWithIcon时,指定的icon正是AFX_IDI_STD_FRAME。
鼠标光标必须调用AfxRegisterWndClass(其中有"Cursor"参数)注册自己的窗口类:然后再将其返回值(一个字符串)作为Create的第一个参数。
奇怪的窗口类名称 Afx:b:14ae:6:3e8f
当应用程序调用CFrameWnd::Create(或CMDIFrameWnd::LoadFrame,第7章)准备产生窗口时,MFC才会在Create或LoadFrame内部调用的PreCreateWindow虚拟函数中为你产生适当的窗口类。这些窗口类的名称分别是
"AfxWnd42d"、"AfxControlBar42d"、"AfxMDIFrame42d"、"AfxFrameCrView42d"、"AfxOleControl42d"
利用spy++(vc++所附的一个工具)观察窗口类的名称时,Application Framework 将把这些窗口类的名称转换为Afx:x:y:z:w的类型,成为独一无二的窗口类名称
x:窗口风格(window style)的hex值
y:窗口鼠标光标hex值
z:窗口后台颜色的hex值
w:窗口图标(icon)的hex值
如果你要使用原来的(MFC默认的)那些个窗口类,但又希望拥有自己定义的一个有意义的类名称,你可以改写PreCreateWindow虚函数(因为Create和LodFrame的内部会调用它),在其中先利用API函数GetClassInfo获得该类的一个副本,更改其类结构中的lpszClassName字段(甚至更改其hIcon字段),再以AfxRegisterClass重新注册之
窗口显示与更新
CMyFrameWnd::cMyFrameWnd结束后,窗口已经诞生出来;程序流程又回到CMyWinApp::InitInstance,于是调用ShowWindow函数令窗口显示出来,并调用UpdateWindow函数令Hello程序送出WM_PAINT消息。
CWinApp的派生对象被称为application object,可以想见,CWinApp本身就代表一个程序本体。CWinApp声明包含在AFXWIN.H文件中,传统上SDK程序的WinMain所完成的工作现在由CWinApp的三个函数完成:
virtual BOOL InitApplicaton();
virtual BOOL InitInstance();
virtual int Run();
记录主窗口handle的成员变量m_pMainWnd也包含在CWinThread中了(CWinApp的父类,MFC2.5包含在CWinApp中)
CWinApp的定义在AFXWIN.H中
二、CFrameWnd——取代WndProc的地位
传统的SDK窗口函数写法是:
MFC程序有新的做法,我们在Hello程序也为CMyFrameWnd准备了两个消息处理程序,声明如下
OnPaint处理WM_PAINT,后者处理WM_COMMAND的IDM_ABOUT。
引爆——Application object
下图中的theApp就是Hello程序的application object,每一个MFC应用程序都有一个而且也只有这么一个全局对象。
父类CWinApp的构造函数在APPCORE.CPP文件中
theApp配置完成后,WinMain登场。MFC早已准备好并由链接器直接加到应用程序代码中的,如下:_tWinMain函数的“_t”是为了支持Unicode而准备的一个宏。
此外,在DLLMODUL.CPP中有一个DLLMain函数。
AfxWinMain在WinMain.cpp中定义可见Windows程序进入点,程序代码可从MFC的WINMAIN.CPP中获得。
其中,AfxGetApp是一个全局函数,定义于AFXWIN1.INL中
_AFXWIN_INLINE CWinApp* AFXAPI AfxGetApp()
{return afxCurrentWinApp;}
而afxCurrentWinApp 有定义于AFXWIN.H中:
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp
再根据稍早所诉CWinApp::CWinApp中的操作,我们于是知道,AfxGetApp其实就是取得CMyWinApp对象指针。所以,AfxWinMain中这样的操作:
CWinApp* pApp = AfxGetApp();
pApp->InitApplication();
pApp->InitInstance();
nReturnCode = pApp->Run();
导致调用:
CWinApp::InitApplication();//因为CMyWinApp并没有改写InitApplication.
CMyWinApp::InitInstance();//因为CMyWinApp改写了InitInstance
CWinApp::Run();//因为CMyWinApp并没有改写Run
InitApplication应该是注册窗口类的场所,InitInstance应该是产生窗口并显示窗口的场所,Run应该是获得消息并分派消息的场所。
下面分别讨论AfxWinMain的四个主要操作以及引发的行为。
AfxWinInit——AFX内部初始化操作
AfxWinInit是继CWinApp构造函数之后的第一个操作。(定义在APPINIT.CPP)
其中调用的AfxInitThread函数的操作摘要如下(在THRDCORE.CPP中定义)
MFC注册四个窗口类,但不再是AfxWinInit中完成。
CWinApp::InitApplication
因为InitApplication是CWinApp的一个虚拟函数;而在CMyWinApp中并没有改写它,所以pApp->InitApplication()调用的是CWinApp::InitApplication。 此函数在APPCORE.CPP中定义
CMyWinApp::InitInstance
因为InitInstance在CMyWinApp被定义所以pApp->InitInstance();调用CMyWinApp::InitInstance
注意:应用程序一定要改写虚函数InitInstance,因为他在CWinApp中只是个空函数,没有任何内建(默认)操作。
CFrameWin::Create产生主窗口:(并先注册窗口类)
CMyWinApp::InitInstance一开始new了一个CMyFrameWnd对象,准备用作主框架窗口的C++对象。new会引发构造函数:
CMyFrameWnd::CMyFrameWnd
{
Create(NULL,"Hello MFC",WS_OVERLAPPEDWINDOW,rectDefault,NULL,"MainMenu");
}
其中Create 是CFrameWnd的成员函数,它将产生一个窗口。但,使用哪一个窗口类呢?(这里所谓的“窗口类”是由RegisterClass所注册的一份数据结构,不是C++类)。根据CFrameWnd::Create 的规格:
BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle = WS_OVERLAPPEDWINDOW,
const RECT& rect = rectDefault,
CWnd* pParentWnd = NULL,
LPCTSTR lpszMenuName =NULL,
DWORD dwExStyle =0,
CCreateContext* pContext =NULL);
八个参数中的后六个参数都有默认值,只有前两个参数必须指定。
第一个参数lpszClassName指定WNDCLASS窗口类,我们放置NULL代表的意思是要以MFC内建的窗口类产生一个标准的外框窗口。但,此时此刻Hello程序根本不存在任何窗口,Create函数在产生窗口之前会引发窗口类的注册操作,
第二个参数lpszWindowName指定窗口标题。
第三个参数dwStyle指定窗口风格,默认是WS_OVERLAPPEDWINDOW,也正是最常用的一种,他被定义为(在WINDWS.H之中):
#define WS_OVERLAPPEDWINDOW(WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX)
因此,如果你不想要窗口右上角的极大极小钮,就得这么做:
Create(NULL,"Hello MFC",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME,rectDefault,NULL,"MainMenu");
如果希望窗口有垂直滚动条,就得在第三个参数上再增加WS_VSCROLL风格。除了上述标准的另有扩充风格,在Create的第七个参数dwExStyle指定之。扩充风格唯有以::CreateWindowEx(而非::CreateWindow)函数才能完成。其实CFrameWnd::Create最终调用的正是:CreateWindowEx.
Windows3.1提供五种窗口扩充风格:
WS_EX_DLGMODALFRAME、WS_EX_NOPARENTNOTIFY、WS_EX_TOPMOST、WS_EX_ACCEPTFILES、WS_EX_TRANSPARENT
Windows9x有更多选择,包括WS_EX_WINDOWEDGE和WS_EX_CLIENTEDGE,让窗口更具3D立体感。
Create的第四个参数 rect指定窗口的位置与大小。默认值rectDefault是CFrameWnd的一个static成员变量,
可以为Create(NULL,"Hello MFC",WS_OVERLAPPEDWINDOW,CRect(40,60,240,460),NULL,"MainMenu");//起始位置(40,60),宽200,高400
第五个参数pParentWnd指定父窗口。对于一个top-level窗口而言,此值应为NULL,表示没有父窗口(其实是有的,父窗口就是desktop窗口)
第六个参数lpszMenuName指定菜单。本例使用一份在RC中准备好的菜单"MainMenu"。
第八个参数pContext是一个指向CCreateContext结构的指针,framework利用它,在具备Document/View结构的程序中初始化外窗口(第8章)本例不具备Document/View结构默认值为NULL.
具体的Create实现如下在WINFRM.CPP中
在函数中调用CreateEx.注意,CWnd有成员函数CreateEx,但其派生类CFrameWnd并没有改变虚函数CreateEx,所以这里虽然调用的是CFrameWnd::CreateExx,其实是从父类继承下来的CWnd::CreateEx。
函数中调用的PreCreateWindow是虚函数,在CWnd和CFrameWnd中都有定义。由于this指针所指对象的缘故,这里应该调用的是CFrameWnd::PreCreateWindow(还记得第2章的虚函数的那种行为方式)
WINFRM.CPP
其中AfxDeferRegisterClass是一个AFXIMPL.H中的宏
出现在上述函数中的六个窗口类卷标代码,分别定义与AFXIMPL.H中:
#define AFX_WND_REG (0x0001)
#define AFX_WNDCONTROLBAR_REG (0x0002)
#define AFX_WNDMDIFRAME_REG (0x0004)
#define AFX_WNDFRAMEORVIEW_REG (0x0008)
#define AFX_WNDCOMMCTLS_REG (0x0010)
#define AFX_WNDOLECONTROL_REG (0x0020)
出现在上述函数中的五个窗口类名称,分别定义与WINCORE.CPP中:
const TCHAR _afxWnd[] =AFX_WND;
const TCHAR _afxWndControlBar[] =AFX_WNDCONTROLBAR;
const TCHAR _afxWndMDIFrame[] =AFX_WNDMDIFRAME;
const TCHAR _afxWndFrameOrView[] =AFX_WNDFRAMEORVIEW;
const TCHAR _afxWndOleControl[] =AFX_WNDOLECONTROL;
等号右边的那些AFX_常数又定义于AFXIMPL.H中:
AfxEndDeferRegisterClass的操作。它调用两个函数完成实际的窗口类注册操作,一个是RegisterWithIcon,一个是AfxRegisterClass:
注意,不同类的PreCreateWindow成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类。如果我们指定类是NULL,那么就使用系统默认类。从CWnd及其各个派生类的PreCreateWindow成员函数可以看出,整个Framework针对不同功能的窗口使用了那些窗口类:
指定图标和光标形状实为RegisterClass责任而非CreateWindow的责任!
MFC开放了一个窗口,可以在HELLO.RC中这样设定图标:
AFX_IDI_STDFRAME ICON DISCARDABLE "HELLO.ICO"
你可以从AfxEndDeferRegisterClass的第55行看出,当它调用RegisterWithIcon时,指定的icon正是AFX_IDI_STD_FRAME。
鼠标光标必须调用AfxRegisterWndClass(其中有"Cursor"参数)注册自己的窗口类:然后再将其返回值(一个字符串)作为Create的第一个参数。
奇怪的窗口类名称 Afx:b:14ae:6:3e8f
当应用程序调用CFrameWnd::Create(或CMDIFrameWnd::LoadFrame,第7章)准备产生窗口时,MFC才会在Create或LoadFrame内部调用的PreCreateWindow虚拟函数中为你产生适当的窗口类。这些窗口类的名称分别是
"AfxWnd42d"、"AfxControlBar42d"、"AfxMDIFrame42d"、"AfxFrameCrView42d"、"AfxOleControl42d"
利用spy++(vc++所附的一个工具)观察窗口类的名称时,Application Framework 将把这些窗口类的名称转换为Afx:x:y:z:w的类型,成为独一无二的窗口类名称
x:窗口风格(window style)的hex值
y:窗口鼠标光标hex值
z:窗口后台颜色的hex值
w:窗口图标(icon)的hex值
如果你要使用原来的(MFC默认的)那些个窗口类,但又希望拥有自己定义的一个有意义的类名称,你可以改写PreCreateWindow虚函数(因为Create和LodFrame的内部会调用它),在其中先利用API函数GetClassInfo获得该类的一个副本,更改其类结构中的lpszClassName字段(甚至更改其hIcon字段),再以AfxRegisterClass重新注册之
窗口显示与更新
CMyFrameWnd::cMyFrameWnd结束后,窗口已经诞生出来;程序流程又回到CMyWinApp::InitInstance,于是调用ShowWindow函数令窗口显示出来,并调用UpdateWindow函数令Hello程序送出WM_PAINT消息。