天天看點

視窗子類化與超類化

模闆類 CWindowImplBaseT 提供一個資料成員 WNDPROC m_pfnSuperWindowProc 并且初始化為 ::DefWindowProc 。然而在視窗超類化處理時它存儲了已注冊視窗類的視窗過程,在視窗子類化時它儲存視窗執行個體句柄原有的視窗過程,所有設定了  bHandled = false 的消息都由該資料成員處理。

template <class TBase = CWindow, class TWinTraits = CControlWinTraits>

class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >

{

public:

WNDPROC m_pfnSuperWindowProc;

CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)

{}

···

}

子類化 Subclass

子類化是在視窗執行個體建立之後,把視窗執行個體的視窗過程用另一個使用者定義視窗類的視窗過程函數替換,進而改變其視窗行為。而視窗執行個體的原視窗過程則儲存在資料成員 m_pfnSuperWindowProc 中。

template <class TBase, class TWinTraits>

BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hWnd)

{

BOOL result;

ATLASSUME(m_hWnd == NULL);

ATLASSERT(::IsWindow(hWnd));

// Allocate the thunk structure here, where we can fail gracefully.

result = m_thunk.Init(GetWindowProc(), this);

if (result == FALSE)

{

return FALSE;

}

WNDPROC pProc = m_thunk.GetWNDPROC();

WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);

if(pfnWndProc == NULL)

return FALSE;

m_pfnSuperWindowProc = pfnWndProc;

m_hWnd = hWnd;

return TRUE;

}

視窗子類化函數接受一個有效視窗執行個體句柄 hWnd ,此視窗句柄必須是一個根據某個視窗類建立的視窗(如果是對話框模闆資源上的控件視窗,則必然滿足此要求)。

1 )把此視窗句柄 hWnd 對應的視窗類(超類視窗 Superclass )的視窗過程設定為調用者視窗類(子類視窗 Subclass )的視窗過程函數。

2 ) hWnd 原有視窗過程函數由資料成員 m_pfnSuperWindowProc 儲存,便于把未處理的消息傳遞到原有視窗過程處理。

3 )調用者視窗類與視窗句柄 hWnd 挂接 m_hWnd = hWnd。

視窗子類化的行為在程式運作期有效。當視窗接收到 WM_NCDESTROY 消息即将被銷毀時,如果該視窗存在視窗子類化的行為,則對此視窗類進行去子類化處理,即恢複 hWnd 對應的原視窗類的原視窗過程,并且視窗辨別設定 WINSTATE_DESTROYED 位。具體實作如下:

template <class TBase, class TWinTraits>

LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

···

// pass to the message map to process

LRESULT lRes;

BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);

// restore saved value for the current message

ATLASSERT(pThis->m_pCurrentMsg == &msg);

// do the default processing if message was not handled

if(!bRet)

{

if(uMsg != WM_NCDESTROY)

lRes = pThis->DefWindowProc(uMsg, wParam, lParam);

else

{

// unsubclass, if needed

LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);

lRes = pThis->DefWindowProc(uMsg, wParam, lParam);

if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)

::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);

// mark window as destryed

pThis->m_dwState |= WINSTATE_DESTROYED;

}

}

···

return lRes;

}

首先将消息傳遞到消息映射函數處理:

ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);

此函數是 CMessageLoop 接口類的唯一虛拟函數,完成具體的消息處理任務,在具體視窗類中由 BEGIN_MSG_MAP ( wndclass ) /END_MSG_MAP ()宏定義實作:

#define BEGIN_MSG_MAP(theClass) /

public: /

BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) /

{ /

BOOL bHandled = TRUE; /

(hWnd); /

(uMsg); /

(wParam); /

(lParam); /

(lResult); /

(bHandled); /

switch(dwMsgMapID) /

{ /

case 0:

#define ALT_MSG_MAP(msgMapID) /

break; /

case msgMapID:

#define MESSAGE_HANDLER(msg, func) /

if(uMsg == msg) /

{ /

bHandled = TRUE; /

lResult = func(uMsg, wParam, lParam, bHandled); /

if(bHandled) /

return TRUE; /

}

#define END_MSG_MAP() /

break; /

default: /

ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)/n"), dwMsgMapID); /

ATLASSERT(FALSE); /

break; /

} /

return FALSE; /

}

可見所有的消息映射處理函數都在 ProcessWindowMessage ()中被按照相應的消息來調用,處理結果 LRESULT 由該函數的參數傳回。此函數中還定義了一個極其重要的變量 bHandled ,它決定了此函數的傳回值。該變量作為參數傳遞給消息處理函數,是以可以在消息映射函數中被設定。在視窗過程函數中若 ProcessWindowMessage()   傳回 TRUE 則此消息處理到此結束,否則調用視窗類預設視窗函數:

LRESULT DefWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)

{

#ifdef STRICT

return ::CallWindowProc(m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);

#else

return ::CallWindowProc((FARPROC)m_pfnSuperWindowProc, m_hWnd, uMsg, wParam, lParam);

#endif

}

僅僅是調用資料成員 m_pfnSuperWindowProc 儲存的視窗過程函數進行處理。前面提到在視窗超類化處理時它存儲了已注冊視窗類的視窗過程,在視窗子類化時它儲存視窗執行個體句柄原有的視窗過程,所有 bHandled = false 的消息都由該資料成員處理,也就是說超類化時路由到已注冊視窗類視窗過程繼續處理,子類化時路由到視窗執行個體句柄原有的視窗過程繼續處理,否則,調用預設視窗過程函數處理。

是以在消息映射函數中可以通過設定 bHandled 來決定是否需要繼續對此消息進行處理。然後視窗銷毀時進行去子類化。當然如果需要在視窗銷毀前去子類化,則可直接調用如下函數去子類化:

template <class TBase, class TWinTraits>

HWND CWindowImplBaseT< TBase, TWinTraits >::UnsubclassWindow(BOOL bForce )

{

ATLASSUME(m_hWnd != NULL);

WNDPROC pOurProc = m_thunk.GetWNDPROC();

WNDPROC pActiveProc = (WNDPROC)::GetWindowLongPtr(m_hWnd, GWLP_WNDPROC);

HWND hWnd = NULL;

if (bForce || pOurProc == pActiveProc)

{

if(!::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, (LONG_PTR)m_pfnSuperWindowProc))

return NULL;

m_pfnSuperWindowProc = ::DefWindowProc;

hWnd = m_hWnd;

m_hWnd = NULL;

}

return hWnd;

}

首先判斷視窗執行個體句柄現有的視窗過程是否是目前視窗類的視窗過程,即是否存在子類化。若是并且強制要求去子類化,則恢複視窗執行個體句柄原有的視窗過程,并設定 m_pfnSuperWindowProc = ::DefWindowProc 存儲預設視窗過程。最後視窗類與視窗執行個體句柄分離 m_hWnd = NULL。傳回視窗執行個體句柄。

超類化 superclassing

超類化是根據已注冊的視窗類來建立一個新的視窗類,這一過程是一個複制的過程,建立視窗類複制了已注冊視窗類的所有内容,但是把視窗類名和視窗過程改為自己預定義的視窗類名和視窗過程。 然後注冊建立視窗類, 同時設定資料成員 m_pfnSuperWindowProc 為已注冊視窗類的視窗過程。

#define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) /

static ATL::CWndClassInfo& GetWndClassInfo() /

{ /

static ATL::CWndClassInfo wc = /

{ /

{ sizeof(WNDCLASSEX), 0, StartWindowProc, /

  0, 0, NULL, NULL, NULL, NULL, NULL, WndClassName, NULL }, /

OrigWndClassName, NULL, NULL, TRUE, 0, _T("") /

}; /

return wc; /

}

超類化宏設定了視窗類資訊結構體的成員變量 OrigWndClassName 為一個已注冊視窗類的視窗類名。

  CWindowImpl :: Create()函數中注冊視窗類然後建立視窗執行個體。

ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc);

注冊視窗類時傳遞了 m_pfnSuperWindowProc 作為參數,最終調用的是如下模闆函數

template <class T>

ATLINLINE ATOM AtlModuleRegisterWndClassInfoT(_ATL_BASE_MODULE* pBaseModule, _ATL_WIN_MODULE* pWinModule, typename T::_ATL_WNDCLASSINFO* p, WNDPROC* pProc, T)

{

···

if(p->m_atom == 0)

{

if (p->m_lpszOrigName != NULL)

{

ATLASSERT(pProc != NULL);

T::PCXSTR lpsz = p->m_wc.lpszClassName;

WNDPROC proc = p->m_wc.lpfnWndProc;

T::WNDCLASSEX wc;

wc.cbSize = sizeof(T::WNDCLASSEX);

// Try global class

if(!T::GetClassInfoEx(NULL, p->m_lpszOrigName, &wc))

{

// try process local

if(!T::GetClassInfoEx(pBaseModule->m_hInst, p->m_lpszOrigName, &wc))

{

ATLTRACE(atlTraceWindowing, 0, "ERROR : Could not obtain Window Class information for %s/n", p->m_lpszOrigName);

return 0;

}

}

p->m_wc = wc;

p->pWndProc = p->m_wc.lpfnWndProc;

p->m_wc.lpszClassName = lpsz;

p->m_wc.lpfnWndProc = proc;

}

···

T::WNDCLASSEX wcTemp;

wcTemp = p->m_wc;

p->m_atom = static_cast<ATOM>(T::GetClassInfoEx(p->m_wc.hInstance, p->m_wc.lpszClassName, &wcTemp));

if (p->m_atom == 0)

{

p->m_atom = T::RegisterClassEx(pWinModule, &p->m_wc);

}

}

if (p->m_lpszOrigName != NULL)

{

ATLASSERT(pProc != NULL);

ATLASSERT(p->pWndProc != NULL);

*pProc = p->pWndProc;

}

return p->m_atom;

}

函數判斷如果有視窗超類化(p->m_lpszOrigName != NULL)則擷取已注冊視窗類的資訊 wc ,然後

p->m_wc = wc;

p->pWndProc = p->m_wc.lpfnWndProc;

p->m_wc.lpszClassName = lpsz;

p->m_wc.lpfnWndProc = proc;

以上語句的作用是:

令建立視窗類的 pWndProc 變量儲存已注冊視窗類視窗函數位址。

建立視窗類僅僅保留視窗類名和視窗過程函數不變,其餘的都用子類視窗類相關資訊替換。

注冊建立視窗類,令建立視窗類 m_pfnSuperWindowProc 成員資料儲存已注冊視窗類的視窗過程函數位址。傳回注冊結果。