首先需要知道,消息是怎么来的。windows操作系统主要依赖消息与应用程序通讯。消息有三类, 一类是操作系统发出的消息,例如用户改变了显示器的显示模式,操作系统就会向所有的应用程序发送这个消息。还有是应用程序运行过程中,比如建立窗口,或者销毁窗口,那么相应的窗口就会接到这类消息,还有一种消息是用户交互过程中,例如用户移动鼠标,按下键盘按键,当前的活动窗口就会得到这类消息。其实用户还可以自己定义消息,通过PostMessage或者SendMessage发送给窗口,这类消息类似于用户交互过程中的消息。我就把它归到这一类里面去了。
操作系统拥有一个消息队列,消息出现时候,会首先送到消息队列中,然后操作系统负责依次将这些消息发送给相应的窗口。
windows提供了许多具有特定功能的窗口,例如工具条,按钮,弹出列表框,rebar等, 大家通常将他们称为控件。他们以可执行代码的方式存在在系统中,可能在某个DLL里面。你注册了之后,就可以使用。在你的应用中,用户会使用那些控件,例如按下一个按钮。这时候,按钮控件,会将消息发送给它的父窗口,通知父窗口:我被按下了,然后期望父窗口响应这个动作,毕竟按钮本身不知道该如何响应。这是一般的做法。
有些情况下,例如我们要拓展控件的功能,比如想让他的外观更Cool一些,这时候我们要继承现有的控件写一个子类,例如绘制这个新的控件的消息,让父窗口来做就不合适了,毕竟我们希望开发一个可重用的控件。这种情况下,Windows提供了一种机制,叫做消息反射。其实质就是说,父窗口接到了按钮的某个消息,并不处理,而是反射回控件本身,控件也是一个窗口,它也有自己的消息处理机制。
下面结合一个实例来说明
class CPicLooker : public CWindowImpl<CPicLooker, CStatic>,
public COwnerDraw<CPicLooker>
{
public:
CPicLooker(void)
{
}
~CPicLooker()
{
}
BEGIN_MSG_MAP(CPicLooker)
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
CHAIN_MSG_MAP_ALT( COwnerDraw<CPicLooker>,1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
public:
LRESULT OnEraseBkgnd(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
return 1;
}
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct )
{
CDCHandle dc = lpDrawItemStruct->hDC;
RECT& rcItem = lpDrawItemStruct->rcItem;
dc.FillRect( &rcItem, 21);
dc.MoveTo( rcItem.left, rcItem.top);
dc.LineTo(rcItem.right, rcItem.bottom);
}
};
这里是 COwnerDraw 的消息映射区。
// Message map and handlers
BEGIN_MSG_MAP(COwnerDraw< T >)
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
ALT_MSG_MAP(1)
MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
END_MSG_MAP()
CownerDraw模板的这两个消息映射区域唯一的不同是1号区域的消息是以OCM开头的。这就涉及到了ATL的消息反射(Message Reflection)机制。所谓消息反射,就是指窗口类在收到消息时可以将消息反传回去给发出消息的窗口类。比如对于一个自画样式的按钮,它会发出WM_DRAWITEM消息通知父窗口,而父窗口并不处理这个消息而是将它反传回去,发送的消息就变成了OCM_DRAWITEM, 让按钮自己处理。显而易见,这种机制更符合面向对象的要求,减少了按钮和父窗口之间的依赖关系。所以我们要在控件的消息处理里面关联1区的消息。
在框架类的消息映射表中加入REFLECT_NOTIFICATIONS()宏,这样就完成了消息映射。但是需要注意的是,
REFLECT_NOTIFICATIONS必须放在消息映射表的最后,否则所有通知消息都将被返回,窗口本身得不任何通知消息。
== over