在窗口消息处理方面,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章阐述。