WTL中的消息循环初探(转载)

怎么让View来响应菜单事件?
在WTL里面,CMainFrame的消息循环是这样的:

程序代码 程序代码

BEGIN_MSG_MAP(CMainFrame)
     MESSAGE_HANDLER(WM_Create, OnCreate)
     CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
     CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
END_MSG_MAP()

而View里面的消息循环是这样的:

程序代码 程序代码

BEGIN_MSG_MAP(CGameView)
     MESSAGE_HANDLER(WM_PAINT, OnPaint)
     MESSAGE_HANDLER(WM_Create, OnCreate)
COMMAND_ID_HANDLER(ID_GAME_START_NEW, OnGameStartNew)
END_MSG_MAP()

虽然我明明写了一个OnGameStartNew来响应菜单项ID_GAME_START_NEW,但是通过实验可以确定,CMainFrame并没有将这个消息转交过来,而是自己截获认为是无用的消息扔掉了。那么,如何让CMainFrame将消息交给View就是我要解决的问题了。
首先我想到的是在PreTranslateMessage这个函数上做文章,因为CMainFrame的PreTranslateMessage确实调用了 View的PreTranslateMessage函数,所以要想截获这个消息还是蛮容易的,但是由于自己要写一个显式的消息循环,写出来的代码极为丑陋,不爽,只把它作为最后保底的方案。
接着我想到的是向消息循环注册Message Filter的方法,也就是在View的OnCreate中加入以下代码:

程序代码 程序代码

// register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);

并且,要让View派生自CMessageFilter,如下所示:

程序代码 程序代码

class CGameView
: public CScrollWindowImpl<CGameView>
, public CMessageFilter
{
// ...
};

但是很可惜,只有FrameWindow才能享用Message Filter带来的灵活性,View不可以。
再接着,我尝试使用CHAIN_MSG_MAP_MEMBER(),直接在CMainFrame的消息循环里面把View的消息循环给插进来:

程序代码 程序代码

BEGIN_MSG_MAP(CMainFrame)
     MESSAGE_HANDLER(WM_Create, OnCreate)
     CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
     CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
CHAIN_MSG_MAP_MEMBER(m_view)
END_MSG_MAP()

不过让我十分沮丧的是,这样可以捕捉菜单消息了,也非常的简洁明了,但是View截获了很多本来不应该收到的消息,使得整个CMainFrame的显示等等都乱了!这可不行!
最后,我找到了CHAIN_MSG_MAP_ALT_MEMBER()和ALT_MSG_MAP(),用它们一起配合,终于让事情变得简洁好用了!
先要改View里面的消息循环,把它写成如下形式:

程序代码 程序代码

BEGIN_MSG_MAP(CGameView)
     MESSAGE_HANDLER(WM_PAINT, OnPaint)
     MESSAGE_HANDLER(WM_Create, OnCreate)
ALT_MSG_MAP(1) // Handle notify message.
     COMMAND_ID_HANDLER(ID_GAME_START_NEW, OnGameStartNew)
END_MSG_MAP()

然后在CMainFrame的消息循环中加入CHAIN_MSG_MAP_ALT_MEMBER():

程序代码 程序代码

BEGIN_MSG_MAP(CMainFrame)
     MESSAGE_HANDLER(WM_Create, OnCreate)
     CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
     CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
CHAIN_MSG_MAP_ALT_MEMBER(m_view, 1)
END_MSG_MAP()

好了,这就可以了。
最后的最后,来废话一下原理:
在ATL/WTL 里面,为了能够将好几个消息循环有选择串起来,特别做了这个msgMapID的东西,消息循环都是先switch (msgMapID),然后在不同的 Map ID中再来寻找对应的消息响应函数。默认的Windows消息始终使用Map ID为0的消息响应函数,而一旦有需要,都可以手动改成指定的 Map ID。注意,不同的Map ID的消息响应函数是互斥的,一次一般只可能使用一个Map(当然,非要手动把多个Map ID加入同一个消息循环的 话也可以……)。
具体的实现代码可以看WTL的源码,如果有一定的Windows编程基础的话,应该非常好懂。
使用Map ID的额外好处:
通过这种机制,我们可以很方便的在CMainFrame中将消息派发到不同的控件当中去,这样就比MFC的消息机制要灵活很多。

posted @ 2009-07-27 10:07  Fan Zhang  阅读(420)  评论(0编辑  收藏