WTL怎么让View来响应菜单事件?


在WTL里面,CMainFrame的消息循环是这样的:
程序代码
BEGIN_MSG_MAP(CMainFrame)
  MESSAGE_HANDLER(WM_Create, OnCreate)
  CHAIN_MSG_MAP(CUpdateUI)
  CHAIN_MSG_MAP(CFrameWindowImpl)
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
  , 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)
  CHAIN_MSG_MAP(CFrameWindowImpl)
  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)
  CHAIN_MSG_MAP(CFrameWindowImpl)
  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的消息机制要灵活很多。


你可能感兴趣的:(WTL怎么让View来响应菜单事件?)