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成员用来记录函数的类型。