CWnd类与Windows窗口的关系-3、CWnd类如何封装Windows窗口

来源:互联网 发布:final 定义数组 编辑:程序博客网 时间:2024/05/21 09:26

现在,可以比较深入地对CWnd类的封装机制进行剖析了。

在建立窗口句柄映射方面,CWnd使用了一个未公开的类CHandleMap进行管理。使用CWnd及派生类创建窗口时,建立了句柄映射,在窗口销毁时删除映射。一个在MFC内部创建的CHandleMap对象管理所有CWnd实例与窗口句柄的映射,该对象通过一个内部使用的全局函数afxMapHWND()创建并取得。

6.3.1  使用操作映射的函数
CHandleMap主要包括3个成员函数:SetPermanent()(建立映射)、RemoveHandle()(删除映射)、LookupPermanent()(在映射中查找与指定句柄对应的对象指针)。下面代码是操作句柄映射的3个CWnd成员函数:

//根据指定的窗口句柄,在映射中查找对应的CWnd对象指针

CWnd* PASCAL CWnd::FromHandlePermanent(HWND hWnd)

{                 //取得映射管理对象

         CHandleMap* pMap = afxMapHWND();

         CWnd* pWnd = NULL;

         if (pMap != NULL)

         {        //在映射中寻找窗口对象

                   pWnd = (CWnd*)pMap->LookupPermanent(hWnd);

                   ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);

         }

         return pWnd;

}

/*根据指定的窗口句柄,首先在映射中查找对应的CWnd对象指针,如果在映射中查找失败,则建立一个临时的CWnd对象,与句柄关联,再将对象指针返回。如果不锁定临时对象,在空闲时将自动删除之。*/

CWnd* PASCAL CWnd::FromHandle(HWND hWnd)

{

//参数为TRUE,如果映射对象没有创建,则自动创建

         CHandleMap* pMap = afxMapHWND(TRUE);

         ASSERT(pMap != NULL);

//先查找永久映射,失败则返回临时对象

         CWnd* pWnd = (CWnd*)pMap->FromHandle(hWnd);

#ifndef _AFX_NO_OCC_SUPPORT

         pWnd->AttachControlSite(pMap);

#endif

         ASSERT(pWnd == NULL || pWnd->m_hWnd == hWnd);

         return pWnd;

}

//建立当前CWnd对象与指定句柄的映射

BOOL CWnd::Attach(HWND hWndNew)

{

//如果建立映射,保证二者是一一对应的

         ASSERT(m_hWnd == NULL);  // 对象是否已建立映射

         ASSERT(FromHandlePermanent(hWndNew) == NULL);//句柄是否已建立映射

         if (hWndNew == NULL)

                   return FALSE;

//参数为TRUE,如果映射对象没有创建,则自动创建

         CHandleMap* pMap = afxMapHWND(TRUE);

         ASSERT(pMap != NULL);

//建立映射关系

         pMap->SetPermanent(m_hWnd = hWndNew, this);

#ifndef _AFX_NO_OCC_SUPPORT

         AttachControlSite(pMap);

#endif

         return TRUE;

}

 

CWnd myWnd;myWnd.Attach(hWnd);

这会建立起一个项目,这个项目是永久性的关联myWnd hWnd的一个映射。调用CWnd::FromHandle(hWnd)将会返回一个指向myWnd的指针。当myWnd 被删除后,析构函数会自动的通过窗口函数DestroyWindow 销毁hWnd。如果你并不愿意这么做,那么hWnd必须在myWnd 的对象被销毁之前同myWnd 相分离。(通常离开myWnd 定义的作用域中)
成员函数Detach 做这些工作。

myWnd.Detach();

 

//删除当前CWnd对象已建立的句柄映射

HWND CWnd::Detach()

{

         HWND hWnd = m_hWnd;

         if (hWnd != NULL)

         {                 CHandleMap* pMap = afxMapHWND();

                   if (pMap != NULL)

                   //删除映射

                            pMap->RemoveHandle(m_hWnd);

         m_hWnd = NULL;

         }

#ifndef _AFX_NO_OCC_SUPPORT

         m_pCtrlSite = NULL;

#endif

         return hWnd;

}

6.3.2  CWnd如何处理窗口消息
在窗口消息处理方面,CWnd使用了窗口子类化和消息映射机制,关于消息映射的知识将在第9章详述,下面着重阐述CWnd是如何应用子类化处理窗口消息的。其实,在6.2节的示例中,CBaseWnd已经使用了与CWnd类似的子类化方法处理窗口消息。成员函数CBaseWnd::SubWindowClass()将窗口过程子类化为CBaseWnd::MyBaseWndProc(),在这个窗口过程中调用CBaseWnd::WindowProc()处理窗口消息。原始的窗口过程存入成员m_Super WndProc中,在默认处理中调用。

CWnd在窗口建立之初,使用AfxWndProc()子类化窗口,在AfxWndProc()中同样调用CWnd::WindowProc()处理窗口消息。不同的是,对于具体的消息处理,CWnd::WindowProc()使用消息映射机制,而不是调用固定的虚拟函数。原始的窗口过程存储在CWnd::m_pfnSuper中,在默认的处理过程CWnd::DefWindowProc()中调用。

下面是与消息处理相关的几个CWnd成员函数:

//用于子类化的窗口过程

LRESULT CALLBACK

AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)

{

         // special message which identifies the window as using AfxWndProc

         if (nMsg == WM_QUERYAFXWNDPROC)

                   return 1;

         // all other messages route through message map

         //通过句柄取得CWnd对象指针

         CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);

         ASSERT(pWnd != NULL);

         ASSERT(pWnd->m_hWnd == hWnd);

         return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);

}

LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,

         WPARAM wParam = 0, LPARAM lParam = 0)

{  ……

//调用CWnd的虚拟成员函数处理消息

         lResult = pWnd->WindowProc(nMsg, wParam, lParam);

         ……

return lResult;

}

//可在类向导中重载的虚拟函数

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)

{

         // OnWndMsg函数处理消息映射,如果在映射中没有发现当前消息的处理函数,则返回false

         LRESULT lResult = 0;

         if (!OnWndMsg(message, wParam, lParam, &lResult))

         //如果当前消息没被映射处理,调用默认处理函数

         lResult = DefWindowProc(message, wParam, lParam);

         return lResult;

}

//默认的消息处理函数,同Default()

LRESULT CWnd::DefWindowProc(UINT nMsg, WPARAM wParam, LPARAM lParam)

{

         if (m_pfnSuper != NULL)

         //调用原始的窗口过程

                   return::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);

         WNDPROC pfnWndProc;

         if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)

                   //调用默认窗口处理过程

                   return::DefWindowProc(m_hWnd, nMsg, wParam, lParam);

         else

                   return::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);

}

除以上几个相关成员函数外,CWnd类还定义了两个公共成员,SubclassWindow()和UnsubclassWindow(),前者用于动态地关联并使用AfxWndProc()子类化Windows窗口,后者执行相反过程。这意味着,可以随时将一个使用WIN32 API创建的窗口,通过调用SubclassWindow()关联到CWnd实例上。因为同时执行了子类化操作,所以此时就如同使用CWnd的Create()函数创建了这个Windows窗口一样。函数的代码如下:

BOOL CWnd::SubclassWindow(HWND hWnd)

{

         //hWnd应该是一个没有与任何CWnd实例关联的Windows窗口

         if (!Attach(hWnd)) //先建立关联,再子类化

                   return FALSE;

         //在子类化前调用这个虚函数,给用户提供编程接口

         PreSubclassWindow();

WNDPROC* lplpfn = GetSuperWndProcAddr();/*取得原始窗口函数。如果当前类尚未子类化窗口,返回NULL*/

         //使用AfxWndProc()子类化该窗口

         WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,

                   (DWORD)AfxGetAfxWndProc());//取得AfxWndProc()子类窗口函数

         ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());

         if (*lplpfn == NULL)

                   *lplpfn = oldWndProc; //保存原始窗口函数,在默认处理时调用

         return TRUE;

}

HWND CWnd::UnsubclassWindow()

{

         ASSERT(::IsWindow(m_hWnd));

//得到原始窗口过程

         WNDPROC* lplpfn = GetSuperWndProcAddr();

         //恢复窗口过程

SetWindowLong(m_hWnd, GWL_WNDPROC, (LONG)*lplpfn);

         *lplpfn = NULL;

         //分离窗口对象和窗口句柄

         return Detach();

}

virtual void CWnd::PreSubclassWindow()

{//该虚拟函数在建立窗口时,子类化前被调用,在CWnd::SubclassWindow中也被调用

//它不执行任何操作,可以重载它,根据需要执行子类化。那样,子类化过程会成为原始的窗口过程,在消息默认处理时被调用

}

对窗口操作的封装是很好理解的,成员CWnd::m_hWnd存储映射的窗口句柄,不同的窗口操作成员函数一般封装同名的WIN32 API,函数通过m_hWnd调用同名API即可。下面列举几个相关的成员函数。

void CWnd::SetWindowText(LPCTSTR lpszString)

         { ASSERT(::IsWindow(m_hWnd)); ::SetWindowText(m_hWnd, lpszString); }

BOOL CWnd::IsIconic() const

         { ASSERT(::IsWindow(m_hWnd)); return ::IsIconic(m_hWnd); }

void CWnd::MoveWindow(int x, int y, int nWidth, int nHeight, BOOL bRepaint)

         { ASSERT(::IsWindow(m_hWnd)); ::MoveWindow(m_hWnd, x, y, nWidth, nHeight, bRepaint); }

BOOL CWnd::SetWindowPos(const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags)

         { ASSERT(::IsWindow(m_hWnd));

 return ::SetWindowPos(m_hWnd, pWndInsertAfter->GetSafeHwnd(),     x, y, cx, cy, nFlags); }

CDC* CWnd::GetWindowDC()

         { ASSERT(::IsWindow(m_hWnd)); return CDC::FromHandle(::GetWindowDC(m_hWnd)); }

int CWnd::ReleaseDC(CDC* pDC)

         { ASSERT(::IsWindow(m_hWnd)); return ::ReleaseDC(m_hWnd, pDC->m_hDC); }

void CWnd::UpdateWindow()

         { ASSERT(::IsWindow(m_hWnd)); ::UpdateWindow(m_hWnd); }

关于CWnd的子窗口管理部分,将在第7章阐述

原创粉丝点击