現在,可以比較深入地對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章闡述