CWnd類 (CWnd 類是類庫的核心)
在類的體系結構中,框架程序提供了CWnd類來封裝窗口的HWND句柄,即使用CWnd類來管理窗口的對象,這包括窗口的創建和銷
毀、窗口的一般行爲和窗口所接收的消息。
CCmdTarget類:
爲了使其它的類也有處理消息的機會,我們封裝一個類CCmdTarget作爲消息處理的終點,即所有從這個類派生的類都有處理消息的能
力。所有有消息處理能力的類都要從CCmdTarget繼承。
CWnd和CCmdTarget都定義在頭文件 <afxwin.h>中。
CCmdTarget的部分結構如下:
class AFX_NOVTABLE CCmdTarget : public CObject
{
DECLARE_DYNAMIC(CCmdTarget)
protected:
public:
// Constructors
CCmdTarget();
};
CWnd的部分結構如下:CWnd類的實現代碼在WINCORE.CPP文件中。
class CWnd : public CCmdTarget
{
DECLARE_DYNCREATE(CWnd)
protected:
static const MSG* PASCAL GetCurrentMessage();
};
窗口句柄映射
一個線程中可能有不止一個窗口,因此也會有多個對應的CWnd對象,每個CWnd對象只響應發送給本窗口的消息。
線程如何將接受到的消息交給不同的CWnd對象?
windows是通過窗口函數將消息發送給應用程序的,窗口函數的第一個參數hWnd指示了接收此消息的窗口,我們只能通過窗口句柄
hWnd的值找到對應的CWnd對象的地址。這就要求:
1.只安排一個窗口函數;這裏的窗口函數的作用僅僅是找到處理該消息的CWnd對象的地址,再把它交給此CWnd對象。
2.記錄窗口句柄到CWnd對象指針的映射關係。
解決上面問題的另一種方法:使用CHandleMap類
windows爲每一個線程維護一個消息隊列。窗口句柄映射是模塊線程私有的,所以我們將記錄了窗口句柄映射的CHandleMap對象定義
在模塊線程狀態類 AFX_MODULE_STATE中。
CWnd類提供4個成員函數來管理窗口句柄映射,這些函數都是先調用afxMapHWND函數得到CHandleMap指針。
1. FromHandle(HWND hWnd):試圖返回指向CWnd對象的指針,
2. FromHandlePermanent(HWND hWnd)
3. Attach(HWND hWndNew): 添加一對映射項
4. Detach(): 移除一對映射項
每創建一個窗口,就調用Attach函數將新的窗口句柄附加到CWnd對象,在此窗口銷燬的時候再調用Detach函數取消上面的附加行爲。
這樣,在整個窗口的生命週期內,就會存在一個此窗口句柄hWnd 到 CWnd對象指針pWnd的映射項。
接下來就是怎麼處理消息了,,,,,,
消息處理函數中可以這樣寫:
CWnd * pWnd = CWnd::FromHandle(hWnd);
return pWnd->WindowProc(nMsg, wParam, lParam);
消息映射
直接在窗口函數WndProc中處理消息很繁瑣,我們希望能夠直接使用類的成員函數響應感興趣的消息。消息和處理消息的成員是一一
對應的,這就是所謂的消息映射。每一對消息和處理消息的成員組成一個映射項,類中所有的映射項連在一起形成消息映射表。
消息映射項:每個消息映射項最基本的內容應該包括消息的值和處理該消息的成員函數。 用結構 AFX_MSGMAP_ENTRY 來描述它。
struct AFX_MSGMAP_ENTRY
{
UINT nMessage; // windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of control id's
UINT_PTR nSig; // signature type (action) or pointer to message #
AFX_PMSG pfn; // routine to call (or special value)
};
typedef void (CCmdTarget::*AFX_PMSG)(void); 在這裏,爲什麼要把消息處理函數都定義成CCmdTarget類的成員函數?所有有消
息處理能力的類都要從CCmdTarget繼承,因爲無法預知用戶定義的消息處理函數的具體類型,所以只好先統一轉化成AFX_PMSG
宏指定的類型。
要想自己的類有處理消息的能力,就必須從CCmdTarget類繼承,而且還必須有自己的消息映射表,這就要求記錄下其基類中的消息映
射表的地址(這樣消息才能向上傳遞)。
消息映射表:記錄基類的消息映射表的地址。用結構 AFX_MSGMAP 來描述。
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); //其基類的消息映射表的地址
const AFX_MSGMAP_ENTRY* lpEntries; //消息映射項的指針
};
幾個宏的定義:在<afxwin.h>中
1.DECLARE_MESSAGE_MAP:聲明消息映射
#define DECLARE_MESSAGE_MAP() \
protected: \
static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
virtual const AFX_MSGMAP* GetMessageMap() const; \
消息映射表中記錄的數據是由用戶填寫的,爲了方便使用,我們用BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 兩個宏代替
實現消息映射的代碼
2.BEGIN_MESSAGE_MAP:
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
PTM_WARNING_DISABLE \
const AFX_MSGMAP* theClass::GetMessageMap() const \
{ return GetThisMessageMap(); } \
const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
{ \
typedef theClass ThisClass; \
typedef baseClass TheBaseClass; \
static const AFX_MSGMAP_ENTRY _messageEntries[] = \
{
3. END_MESSAGE_MAP:結束消息映射,在messageEntries[]中添加一項{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } 來表示數組的
結束。
#define END_MESSAGE_MAP() \
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
}; \
static const AFX_MSGMAP messageMap = \
{ &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
return &messageMap; \
} \
PTM_WARNING_RESTORE
BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP必須配對使用,例如:
BEGIN_MESSAGE_MAP(CMyWnd, CCmdTarget)
{WM_CREATE, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnCreate},
{WM_PAINT, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnPaint},
{WM_DESTROY, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnDestory},
END_MESSAGE_MAP
怎麼給類庫中的類添加消息映射表?後面還會講到怎麼添加消息映射項。
很簡單,只需在類的定義代碼中加入DECLARE_MESSAGE_MAP 宏,就可以添加消息映射表。例如:
class CCmdTarget: public CObject
{
//其它成員
DECLARE_MESSAGE_MAP()
};
因爲CCmdTarget類位於消息映射的最頂層,所以不能直接用BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP這一對紅,而只
能手工添加消息映射的代碼。
消息處理
windows統一用WPARAM 和 LPARAM 兩個參數來描述消息的附加信息。CWnd對象的WindowProc函數在調用消息映射表中的函數
響應windows消息時,它如何知道向這個函數傳遞什麼參數呢?又如何知道該函數是否有返回值呢? 所以,在消息映射項中還要記
錄下函數的類型。
AFX_MSGMAP_ENTRY結構中的nSig成員用來記錄函數的類型。