VC窗口的创建过程.docx
《VC窗口的创建过程.docx》由会员分享,可在线阅读,更多相关《VC窗口的创建过程.docx(15页珍藏版)》请在冰豆网上搜索。
VC窗口的创建过程
VC++窗口的创建过程
在Windows中运行的程序,大多数都有一个或几个可以看得见的窗口,而在这些窗口被创建起来之前,操作系统怎么知道该怎样创建该窗口,以及用户操作该窗口的各种消息交给谁处理呢?
所以VC在调用Windows的API(CreateWindow或者CreateWindowEx)创建窗口之前,要求程序员必须定义一个窗口类(不是传统C++意义上的类)来规定所创建该窗口所需要的各种信息,主要包括:
窗口的消息处理函数、窗口的风格、图标、鼠标、菜单等。
其定义如下:
typedefstructtagWNDCLASSA(注:
该结构为ANSII版本)
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
}WNDCLASSA,*PWNDCLASSA,NEAR*NPWNDCLASSA,FAR*LPWNDCLASSA;
style表示该类窗口的风格,如style=CS_VREDRAW|CS_HREDRAW表示窗口在运动或者调整大小时需要重画,关于其它风格可在MSDN中查到。
lpfnWndProc为一指针,指向用户定义的该窗口的消息处理函数。
cbClsExtra用于在窗口类结构中保留一定空间,用于存在自己需要的某些信息。
cbWndExtra用于在Windows内部保存的窗口结构中保留一定空间。
hInstance表示创建该窗口的程序的运行实体代号(WinMain的参数之一)。
hIcon、hCursor、hbrBackground、lpszMenuName分别表示该窗口的图标、鼠标形状、背景色以及菜单。
lpszClassName表示该窗口类别的名称,即标识该窗口类的标志。
从上面可以看出一个窗口类就对应一个WNDCLASSA结构(这里以ANSII为例),当程序员将该结构按自己要求填写完成后,就可以调用RegisterClass(或RegisterClassEx)函数将该类注册,这样以后凡是要创建该窗口,只需要以该类名(lpszClassName中指定)为参数调用CreateWindow,你看多方便呀,真是一举多得啊!
总结:
窗口结构注册(调用RegisterClass(或RegisterClassEx)函数)后,以后凡是要创建该窗口,只需要以该类名(lpszClassName中指定)为参数调用CreateWindow。
二、传统SDK中的窗口类
既然我们知道了什么是窗口类,那我们就将它放到一个传统的SDK程序中,看看是怎样运行的。
#include
LRESULTCALLBACKWndProc(HWND,UINT,WPARAM,LPARAM);
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,
PSTRszCmdLine,intiCmdShow)
{
staticTCHARszAppName[]=TEXT("HelloWin");
WNDCLASwndclass;
wndclass.style =CS_HREDRAW|CS_VREDRAW;
wndclass.lpfnWndProc=WndProc;
wndclass.cbClsExtra =0;
wndclass.cbWndExtra=0;
wndclass.hInstance =hInstance;
wndclass.hIcon =LoadIcon(NULL,IDI_APPLICATION);
wndclass.hCursor =LoadCursor(NULL,IDC_ARROW);
wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuNam=NULL;
wndclass.lpszClassName=szAppName;
RegisterClass(&wndclass);
hwnd=CreateWindow(szAppName,//windowclassname
TEXT("TheHelloProgram"),//windowcaption
WS_OVERLAPPEDWINDOW,//windowstyle
CW_USEDEFAULT,//initialxposition
CW_USEDEFAULT,//initialyposition
CW_USEDEFAULT,//initialxsize
CW_USEDEFAULT,//initialysize
NULL,//parentwindowhandle
NULL, //windowmenuhandle
hInstance, //programinstancehandle
NULL); //creationparameters
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
returnmsg.wParam;
}
这是一个标准的Windows程序代码,程序被启动后,填写一个窗口类,然后调用RegisterClass将该类注册,接着创建该窗口,最后显示窗口和进入消息循环。
三、MFC中的窗口类
当你看到这里,也许你可能会感到奇怪:
我在用MFC向导做程序时,并没有进行什么窗口类的填写和注册吗?
是的,你没有,但是向导帮你做了。
在展示向导是怎么做的之前,请让我先介绍一下预先知识。
在MFC系统中定义了五个默认的窗口类(这里不包括AFX_WNDCOMMCTLS_REG),分别定义在AFXIMPL.h中:
#defineAFX_WND_REG (0x0001)
#defineAFX_WNDCONTROLBAR_REG (0x0002)
#defineAFX_WNDMDIFRAME_REG (0x0004)
#defineAFX_WNDFRAMEORVIEW_REG (0x0008)
#defineAFX_WNDDOLECONTROL_REG (0x0020)
在WINCORE.cpp定义了这些窗口类对应的字符串名称:
constTCHAR_afxWnd[]=AFX_WND;
constTCHAR_afxWndControlBar[]=AFX_WNDCONTROLBAR;
constTCHAR_afxWndMDIFrame[]=AFX_WNDMDIFRAME;
constTCHAR_afxWndFrameOrView[]=AFX_WNDFRAMEORVIEW;
constTCHAR_afxWndOleControl[]=AFX_WNDOLERONTROL;
在AFXIMPL.h中定义了五个AFX_XXX对应的字符串:
#defineAFX_WND AFX_WNDCLASS("WND")
#defineAFX_WNDCONTROLBAR AFX_WNDCLASS("ControlBar")
#defineAFX_WNDMDIFRAME AFX_WNDCLASS("MDIFrame")
#defineAFX_WNDFRAMEORVIEWAFX_WNDCLASS("FrameOrView")
#defineAFX_WNDOLECONTROL AFX_WNDCLASS("OleControl")
看到这里也许有些心急了,其实上面一堆代码只是定义了五个默认窗口类的字符串名称和二进制名称,具体注册行为在全局函数AfxDeferRegisterClass中:
#defineAfxDeferRegisterClass(fClass)\
((afxRegisteredClasses&fClass)?
TRUE:
AfxEndDeferRegisterClass(fClass)
#defineafxRegisteredClassesAfxGetModuleState()->m_fRegisteredClasses
BOOLAFXAPIAfxEndDeferRegisterClass(shortfClass)
{
WNDCLASSwndCls;
wndCls.lpfnWndProc=DefWindowProc;
if(fClass&AFX_WND_REG)
{
wndCls.lpszClassName=_afxWnd;
AfxRegisterClass(&wndCls);
}elseif(fClass&AFX_WNDOLECONTROL_REG)
{
wndCls.lpszClassName=_afxWndOleControl;
AfxRegisterClass(&wndCls);
}elseif(fClass&AFX_WNDCONTROLBAR_REG)
{
wndCls.lpszClassName=_afxWndControlBar;
AfxRegisterClass(&wndCls);
}elseif(fClass&AFX_WNDMDIFRAME_REG)
{
RegisterWithIcon(&wndCls,_afxWndMDIFrame,AFX_IDI_MDIFRAME);
}elseif(fClass&AFX_WNDFRAMEORVIEW_REG)
{
RegisterWithIcon(&wndCls,_afxWndFrameOrView,AFX_IDI_STD_FRAME);
}elseif(fClass&AFX_WNDCOMMCTLS_REG)
{
InitCommonControls();
}
}
从以上例子可以看出,AfxDeferRegisterClass函数用if/else结构实现各种不同窗口的注册,所以MFC函数窗口注册的时候调用AfxDeferRegisterClass函数就可以了。
从上面的代码可以看出,AfxDeferRegisterClass函数首先判断该窗口类是否注册,如已注册则直接返回,否则调用AfxEndDeferRegisterClass进行注册,即注册要求的默认窗口类。
其中RegisterWithIcon和InitCommonControls最终也是转化为调用AfxRegisterClass,而AfxRegisterClass函数调用RegisterClass进行注册,啊,终于看到SDK中的RegisterClass了,看到它总有一种亲切感!
有了上面的知识,我们就可以很容易摸清MFC是怎样注册窗口类的了!
我们知道Windows上所有看得见的东西,在MFC中都是继承于CWnd类的,而CWnd类创建窗口的成员函数是Create和CreateEx,由于Create最终是调用CreateEx,所以我们只需要看CreateEx函数就行了:
create()-->CreateEx()?
?
CREATESTRUCT
?
?
PreCreateWindow(cs);
|?
?
AfxDeferRegisterClass(AFX_WND_REG)
?
?
CreateWindowEx()
BOOLCWnd:
:
CreateEx(DWORDdwExStyle,LPCSTSTRlpszClassName,
……LPVOIDlpParam)
{
CREATESTRUCTcs;
cs.dwExStyle=dwExStyle;
……
cs.lpCreateParams=lpParam;
PreCreateWindow(cs);
AfxHookWindowCreate(this);
HWNDhWnd=:
:
CreateWindowEx(cs.dwStyle,cs.lpszClass,…,cs.lpCreateParams);
……
}
啊,一看到CreateWindowEx,亲切感又来了,这不是和SDK中的CreateWindow一样嘛,是创建窗口!
既然这样,那么注册窗口肯定在该函数之前,会是谁呢?
如果你做过一点MFC程序,你就会对将眼光停留PreCreateWindow上。
对!
就是它了。
PreCreateWindow函数是CWnd类的一个虚拟函数,提供程序设置待创建窗口的属性(包括窗口类),这样继承于CWnd的类都可以按照自己的要求在PreCreateWindow中设置自己的属性了,而且我们看到MFC也是这样做的:
BOOLCWnd:
:
PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WND_REG);
cs.lpszClass=_afxWnd;
}
returnTRUE;
}
BOOLCFrameWnd:
:
PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
cs.lpszClass=_afxWndFrameOrView;
}
returnTRUE;
}
BOOLCMDIFrameWnd:
:
PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WNDMDIFRAME_REG);
cs.lpszClass=_afxWndMDIFrame;
}
}
BOOLCMDIChildWnd:
:
PreCreateWindow(CREATESTRUCT&cs)
{
returnCFrameWnd:
:
PreCreateWindow(cs);
}
BOOLCView:
:
PreCreateWindow(CREATESTRUCT&cs)
{
if(cs.lpszClass==NULL)
{
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG);
cs.lpszClass=_afxWndFrameOrView;
}
}
就是通过继承的方法,MFC实现常用类的窗口注册(代码并不完全,是从MFC中抽取对我们有意义的一部分代码)。
四、在MFC中注册自己的窗口类
在MFC中创建一个窗口,就必须是继承于CWnd类的,这样你的CMyWnd类自然就有了PreCreateWindow方法。
你想注册有自己个性的窗口类,那么就在该函数中进行吧。
也就是在PreCreateWindow函数中注册自己的窗口类,然后将窗口类的类名以及待创建窗口的其它属性(见CREATESTRUCT结构)填写cs,然后返回系统,供系统创建你的窗口。
用SDK建立类的过程:
填写一个窗口类,然后调用RegisterClass将该类注册,接着创建该窗口,最后显示窗口和进入消息循环。
用MFC建立窗口的过程:
我们知道Windows上所有看得见的东西,在MFC中都是继承于CWnd类的,而CWnd类创建窗口的成员函数是Create和CreateEx,由于Create最终是调用CreateEx,所以我们只需要看CreateEx函数就行了:
create()-->CreateEx()?
?
CREATESTRUCT
?
?
PreCreateWindow(cs);
|?
?
AfxDeferRegisterClass(AFX_WND_REG)
?
?
AfxHookWindowCreate(this);//为窗口关联一个消息处理函数WndProc()
?
?
CreateWindowEx()
**********************************************************************************************
CWnd:
:
CreateEX中HOOK函数作用
最基本的一句话概述,钩子函数起了很大作用。
故事是这样的,有些漫长,也需要些耐心。
MFC中消息分为3类:
1.WM_COMMAND:
所有的UI组件和加速键都会产生这种消息,所有派生于CCmdTarget的类都有能力处理该消息
2.标准消息:
除WM_COMMAND之外的WM_xx消息都是标准消息,派生于CWnd的类都有能力处理该消息
3.控件通知消息:
用于子窗口控件向父窗口发送的消息
在MFC的消息映射表的建立中,通过一组宏,你就可以让自己的类先于父类处理某些Windows消息,这种行为很像虚函数,只是我们重载的内容不是虚函数,而是消息。
推动消息的泵
第一阶段窗口过程
在产生一个窗口的时候,会调用CFrameWnd:
:
Create,所有的故事也都从这里展开。
下面的代码为了简洁,去掉了不相关的代码
BOOLCFrameWnd:
:
Create(…) {
// …
if (!
CreateEx(…)) {
// …
}
// …
}
BOOLCWnd:
:
CreateEx(…) {
// …
AfxHookWindowCreate(this);
HWNDhWnd = :
:
CreateWindowEx(…);
// …
}
void AFXAPIAfxHookWindowCreate(CWnd* pWnd) {
// …
if (pThreadState->m_hHookOldCbtFilter == NULL) {
pThreadState->m_hHookOldCbtFilter = :
:
SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook,NULL,:
:
GetCurrentThreadId());
// …
}
// …
pThreadState->m_pWndInit = pWnd;
}
这样,通过AfxHookWindowCreate,在当前线程中安装了一个钩子,用来拦截和窗口相关的事件,每当:
1.另一个窗口成为active;
2.产生或摧毁一个窗口
3.Minimize或maximize一个窗口;
4.移动或缩放一个窗口;
5.完成一个来自系统菜单的命令;
6.从系统队列中取出一个消息;
时,都会先调用_AfxCbtFilterHook(即每当有一个可能引发消息发生的事件的时候都会调用_AfxCbtFilterHook,然后这个函数对这些消息进行过滤,能够处理的就交给AfxGetAfxWndProc,不能处理的就交给全局的DefWndProc()函数),接下来看看钩子函数作了什么:
LRESULTCALLBACK
_AfxCbtFilterHook(int code,WPARAMwParam,LPARAMlParam) {
// …
WNDPROCafxWndProc = AfxGetAfxWndProc();
oldWndProc=(WNDPROC)SetWindowLongPtr(hWnd,GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
// …
}
WNDPROCAFXAPIAfxGetAfxWndProc() {
// …
return &AfxWndProc;
}
这样,_AfxCbtFilterHook的工作总结起来就是通过窗口子类化,把新建的窗口的窗口过程设置成AfxWndProc。
到这里,我们终于找到了窗口过程。
结论
CFrameWnd:
:
Create创建窗口调用CWnd:
:
CreateEx
CWnd:
:
CreateEx调用AfxHookWindowCreate准备为窗口设置钩子
AfxHookWindowCreate调用:
:
SetWindowHookEx为窗口设置了一个WH_CBT类型的钩子来过滤消息,并把过滤函数设置成_AfxCbtFilterHook
_AfxCbtFilterHook通过窗口子类化设置窗口的窗口过程为AfxWndProc
这样,通过:
:
DispatchMessage发送给窗口的消息就会源源不断地送到AfxWndProc中来,可以想到,AfxWndProc利用MFC的消息映射表,分门别类的对消息进行分流。
即每当有一个可能引发消息发生的事件的时候都会调用_AfxCbtFilterHook,然后这个函数对这些消息进行过滤,能够处理的就交给AfxGetAfxWndProc,不能处理的就交给全局的DefWndProc()函数
OnNcCreate,当CWnd