MFC架构之CWnd类

   在Windows系统里,一个窗口的属性分两个地方存放:一部分放在“窗口类”里头,如上所述的在注册窗口时指定;另一部分放在Windows Object本身,如:窗口的尺寸,窗口的位置(X,Y轴),窗口的Z轴顺序,窗口的状态(ACTIVE,MINIMIZED,MAXMIZED,RESTORED…),和其他窗口的关系(父窗口,子窗口…),窗口是否可以接收键盘或鼠标消息,等等。
   为了表达所有这些窗口的共性,MFC设计了一个窗口基类CWnd。有一点非常重要,那就是CWnd提供了一个标准而通用的MFC窗口过程,MFC下所有的窗口都使用这个窗口过程。至于通用的窗口过程却能为各个窗口实现不同的操作,那就是MFC消息映射机制的奥秘和作用了。这些,将在后面有关章节详细论述。
    CWnd提供了一系列成员函数,或者是对Win32相关函数的封装,或者是CWnd新设计的一些函数。这些函数大致如下。

(1)窗口创建函数
这里主要讨论函数Create和CreateEx。它们封装了Win32窗口创建函数::CreateWindowEx。Create的原型如下:
BOOL CWnd::Create(LPCTSTR lpszClassName,LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,CWnd* pParentWnd, UINT nID,CCreateContext* pContext)
Create是一个虚拟函数,用来创建子窗口(不能创建桌面窗口和POP UP窗口)。CWnd的基类可以覆盖该函数,例如边框窗口类等覆盖了该函数以实现边框窗口的创建,视类则使用它来创建视窗口。
Create调用了成员函数CreateEx。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)
CreateEx有11个参数,它将调用::CreateWindowEx完成窗口的创建,这11个参数对应地传递给::CreateWindowEx。参数指定了窗口扩展风格、“窗口类”、窗口名、窗口大小和位置、父窗口句柄、窗口菜单和窗口创建参数。
在CreateEx函数中还有一个虚函数PreCreateWindow。
窗口创建时发送WM_CREATE消息,消息参数lParam指向一个CreateStruct结构的变量,该结构有11个域,其描述见后面4.4.1节对窗口过程的分析,Windows使用和CreateEx参数一样的内容填充该变量。

(2)窗口创建过程

      MFC中窗口创建主要涉及三个重要的函数,分别是CWnd::CreateEx(或者CWnd::Create)、AfxHookWindowCreate、AfxCbtFilterHook函数,首先是大概介绍下MFC的窗口创建过程,当CWnd::CreateEx被调用时,CWnd::CreateEx在调用API函数::CreateWindowEx创建窗口前会通过调用AfxHookWindowCreate安装一个名为_AfxCbtFilterHook的线程钩子,并将需要创建的窗口的CWnd指针保存到线程状态结构中,在API函数::CreateWindowEx真正创建窗口前AfxCbtFilterHook会被调用,AfxCbtFilterHook会执行子类化操作,把要创建的窗口的窗口过程子类化为线程状态结构中的窗口过程(AfxGetModuleState()->m_pfnAfxWndProc),即MFC的标准窗口过程,这样MFC的窗口(CWnd及其派生类)都可以通过消息映射机制接收和响应包括从创建开始的各种各样的消息。

1.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) { 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; } //Hook窗口的创建过程:主要是给当前现成安装一个名为_AfxCbtFilterHook的线程钩子,并讲需要创建的窗口的CWnd指针保存到线程状态结构(_AFX_THREAD_STATE)中。 AfxHookWindowCreate(this); //开始创建窗口,在该函数正真开始创建窗口之前,AfxCbtFilterHook会被调用,AfxCbtFilterHook会执行子类化操作,把要创建的窗口的窗口过程子类化为线程状态结构中的窗口过程 //(AfxGetModuleState()->m_pfnAfxWndProc),这样MFC的窗口(CWnd及其派生类)都可以接收和响应包括从创建开始的各种各样的消息 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); #ifdef _DEBUG if (hWnd == NULL) { TRACE1("Warning: Window creation failed: GetLastError returns 0x%8.8X/n", GetLastError()); } #endif //解除创建窗口的Hook if (!AfxUnhookWindowCreate()) PostNcDestroy(); if (hWnd == NULL) return FALSE; ASSERT(hWnd == m_hWnd); // should have been set in send msg hook return TRUE; }

2.AfxHookWindowCreate
AfxHookWindowCreate主要是Hook窗口的创建过程:主要是给当前现成安装一个名为_AfxCbtFilterHook的线程钩子,并讲需要创建的窗口的CWnd指针保存到线程状态结构

void AFXAPI AfxHookWindowCreate(CWnd* pWnd) { //获取线程状态 _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); //如果线程状态里的m_pWndInit成员与pWnd相等表明给窗口正在创建中,直接返回 if (pThreadState->m_pWndInit == pWnd) return; if (pThreadState->m_hHookOldCbtFilter == NULL) { //给本线程安装一个名为AfxCbtFilterHook的钩子,AfxCbtFilterHook会在::CreateWindowEx真正开始创建窗口前被调用 pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId()); if (pThreadState->m_hHookOldCbtFilter == NULL) AfxThrowMemoryException(); } ASSERT(pThreadState->m_hHookOldCbtFilter != NULL); ASSERT(pWnd != NULL); ASSERT(pWnd->m_hWnd == NULL); // only do once ASSERT(pThreadState->m_pWndInit == NULL); // hook not already in progress //把需要Hook创建的窗口指针保存到线程状态中,AfxCbtFilterHook被调用时会用到。 pThreadState->m_pWndInit = pWnd; }

3.AfxCbtFilterHook
AfxCbtFilterHook会执行子类化操作,把要创建的窗口的窗口过程子类化为线程状态结构中的窗口过程 (AfxGetModuleState()->m_pfnAfxWndProc),即MFC的标准窗口过程,这样MFC的窗口(CWnd及其派生类)都可以接收和响应包括从创建开始的各种各样的消息

LRESULT CALLBACK_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam) { _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData(); //不是HCBT_CREATEWND,传递给下一个HOOK if (code != HCBT_CREATEWND) { return CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code,wParam, lParam); } ASSERT(lParam != NULL); LPCREATESTRUCT lpcs = ((LPCBT_CREATEWND)lParam)->lpcs; ASSERT(lpcs != NULL); //获取要创建的窗口 CWnd* pWndInit = pThreadState->m_pWndInit; BOOL bContextIsDLL = afxContextIsDLL; if (pWndInit != NULL || (!(lpcs->style & WS_CHILD) && !bContextIsDLL)) { // Note: special check to avoid subclassing the IME window if (_afxDBCS) { // check for cheap CS_IME style first... if (GetClassLong((HWND)wParam, GCL_STYLE) & CS_IME) goto lCallNextHook; //获取窗口类 // get class name of the window that is being created LPCTSTR pszClassName; TCHAR szClassName[_countof("ime")+1]; if (HIWORD(lpcs->lpszClass)) { pszClassName = lpcs->lpszClass; } else { szClassName[0] = '/0'; GlobalGetAtomName((ATOM)lpcs->lpszClass, szClassName, _countof(szClassName)); pszClassName = szClassName; } // a little more expensive to test this way, but necessary... if (lstrcmpi(pszClassName, _T("ime")) == 0) goto lCallNextHook; } ASSERT(wParam != NULL); // should be non-NULL HWND //获取即将创建的窗口句柄 HWND hWnd = (HWND)wParam; WNDPROC oldWndProc; if (pWndInit != NULL) { #ifdef _AFXDLL AFX_MANAGE_STATE(pWndInit->m_pModuleState); #endif //确保窗口句柄未与窗口(CWnd)关联 // the window should not be in the permanent map at this time ASSERT(CWnd::FromHandlePermanent(hWnd) == NULL); //使窗口句柄与窗口(CWnd)关联, pWndInit->Attach(hWnd); //CWnd子类可以重载这个函数以在子类化进行前做一些处理 pWndInit->PreSubclassWindow(); //获取窗口的原始窗口过程,该窗口过程保存在CWnd:: m_pfnSuper里,用CWnd::GetSuperWndProcAddr()函数可以获取,该函数为虚函数,可以重载把原来的窗口过程替换。 WNDPROC *pOldWndProc = pWndInit->GetSuperWndProcAddr(); ASSERT(pOldWndProc != NULL); #ifndef _AFX_NO_CTL3D_SUPPORT _AFX_CTL3D_STATE* pCtl3dState; DWORD dwFlags; if (!afxData.bWin4 && !bContextIsDLL &&(pCtl3dState = _afxCtl3dState.GetDataNA()) != NULL && pCtl3dState->m_pfnSubclassDlgEx != NULL &&(dwFlags = AfxCallWndProc(pWndInit, hWnd, WM_QUERY3DCONTROLS)) != 0) { // was the class registered with AfxWndProc? WNDPROC afxWndProc = AfxGetAfxWndProc(); BOOL bAfxWndProc = ((WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC) == afxWndProc); pCtl3dState->m_pfnSubclassDlgEx(hWnd, dwFlags); // subclass the window if not already wired to AfxWndProc if (!bAfxWndProc) { // subclass the window with standard AfxWndProc oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)afxWndProc); ASSERT(oldWndProc != NULL); *pOldWndProc = oldWndProc; } } else #endif { // 通过AfxGetAfxWndProc()获取MFC的标准窗口过程,该窗口过程保存在线程状态的m_pfnAfxWndProc成员中 // subclass the window with standard AfxWndProc WNDPROC afxWndProc = AfxGetAfxWndProc(); //子类化窗口,即把要创建的窗口的窗口过程设为标准的MFC窗口过程,这样标准MFC窗口过程就会通过MFC的消息映射给窗口(CWnd)发送各种消息了。 oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc); ASSERT(oldWndProc != NULL); //保存子类化前的窗口过程,保存在CWnd:: m_pfnSuper成员中 if (oldWndProc != afxWndProc) *pOldWndProc = oldWndProc; } //结束窗口创建的Hook pThreadState->m_pWndInit = NULL; } else { ASSERT(!bContextIsDLL); // should never get here // subclass the window with the proc which does gray backgrounds oldWndProc = (WNDPROC)GetWindowLong(hWnd, GWL_WNDPROC); if (oldWndProc != NULL && GetProp(hWnd, _afxOldWndProc) == NULL) { SetProp(hWnd, _afxOldWndProc, oldWndProc); if ((WNDPROC)GetProp(hWnd, _afxOldWndProc) == oldWndProc) { GlobalAddAtom(_afxOldWndProc); SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)(pThreadState->m_bDlgCreate ? _AfxGrayBackgroundWndProc : _AfxActivationWndProc)); ASSERT(oldWndProc != NULL); } } } } lCallNextHook:LRESULT lResult = CallNextHookEx(pThreadState->m_hHookOldCbtFilter, code, wParam, lParam); #ifndef _AFXDLL if (bContextIsDLL) { ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter); pThreadState->m_hHookOldCbtFilter = NULL; } #endif return lResult; }

 

(3)窗口销毁函数
例如:
DestroyWindow函数 销毁窗口
PostNcDestroy( ),销毁窗口后调用,虚拟函数

(4)用于设定、获取、改变窗口属性的函数,例如:
SetWindowText(CString tiltle) 设置窗口标题
GetWindowText() 得到窗口标题
SetIcon(HICON hIcon, BOOL bBigIcon);设置窗口像标
GetIcon( BOOL bBigIcon ) ;得到窗口像标
GetDlgItem( int nID);得到窗口类指定ID的控制子窗口
GetDC(); 得到窗口的设备上下文
SetMenu(CMenu *pMenu); 设置窗口菜单
GetMenu();得到窗口菜单

(5)用于完成窗口动作的函数

用于更新窗口,滚动窗口,等等。一部分成员函数设计成或可重载(Overloaded)函数,或虚拟(Overridden)函数,或MFC消息处理函数。这些函数或者实现了一部分功能,或者仅仅是一个空函数。如:

* 有关消息发送的函数:
SendMessage( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 ); 给窗口发送发送消息,立即调用方式
PostMessage(( UINT message,WPARAM wParam = 0, LPARAM lParam = 0 );给窗口发送消息,放进消息队列

* 有关改变窗口状态的函数
MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );移动窗口到指定位置
ShowWindow(BOOL );显示窗口,使之可见或不可见

* 实现MFC消息处理机制的函数:
virtual LRESULT WindowProc( UINT message, WPARAM wParam, LPARAM lParam ); 窗口过程,虚拟函数
virtual BOOL OnCommand( WPARAM wParam, LPARAM lParam );处理命令消息
OnNotify               处理通知消息
DefWindowProc          消息的缺省处理
OnChildNotify          消息反射处理

* 消息处理函数:
OnCreate( LPCREATESTRUCT lpCreateStruct );MFC窗口消息处理函数,窗口创建时由MFC框架调用
OnClose();MFC窗口消息处理函数,窗口创建时由MFC框架调用

* 其他功能的函数
CWnd的导出类是类型更具体、功能更完善的窗口类,它们继承了CWnd的属性和方法,并提供了新的成员函数(消息处理函数、虚拟函数、等等)。


1. 在MFC下创建一个窗口对象

MFC下创建一个窗口对象分两步,首先创建MFC窗口对象,然后创建对应的Windows窗口。在内存使用上,MFC窗口对象可以在栈或者堆(使用new创建)中创建。具体表述如下:
* 创建MFC窗口对象。通过定义一个CWnd或其派生类的实例变量或者动态创建一个MFC窗口的实例,前者在栈空间创建一个MFC窗口对象,后者在堆空间创建一个MFC窗口对象。
* 调用相应的窗口创建函数,创建Windows窗口对象。

例如:在前面提到的AppWizard产生的源码中,有CMainFrame(派生于CMDIFrame(SDI)或者CMDIFrameWnd(MDI))类。它有两个成员变量定义如下:
CToolBar m_wndToolBar;
CStatusBar m_wndStatusBar;
当创建CMainFrame类对象时,上面两个MFC Object也被构造。
CMainFrame还有一个成员函数
OnCreate(LPCREATESTRUCT lpCreateStruct),
它的实现包含如下一段代码,调用CToolBar和CStatusBar的成员函数Create来创建上述两个MFC对象对应的工具栏HWND窗口和状态栏HWND窗口.

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { … if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) { TRACE0("Failed to create toolbar/n"); return -1; // fail to create } if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } … }

你可能感兴趣的:(MFC编程)