反射通知消息
如果你是用CWindowImpl的派生类封装控件,比如前面使用的CEditImpl,你可以在类的内部处理通知消息而不是在对话框中,这就是通知消息的反射,它和MFC的消息反射相似。不同的是在WTL中父窗口和控件都可以处理通知消息,而在MFC中只有控件能处理通知消息(译者加:除非你重载 WindowProc函数,在MFC反射这些消息之前截获它们)。
如果需要将通知消息反射给控件封装类,只需在对话框的消息映射链中添加REFLECT_NOTIFICATIONS()宏:
class CMainDlg : public ... { public: BEGIN_MSG_MAP_EX(CMainDlg) //... NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged) REFLECT_NOTIFICATIONS() END_MSG_MAP() }; |
这个宏向消息映射链添加了一些代码处理那些未被前面的宏处理的通知消息,它检查消息传递的HWND窗口句柄是否有效并将消息转发给这个窗口,当然,消息代码的数值被改变成OLE控件所使用的值,OLE控件有与之相似的消息反射系统。新的消息代码值用OCM_xxx代替了WM_xxx,但是消息的处理方式和未反射前一样。
有18种被反射的消息:
·控件通知消息: WM_COMMAND, WM_NOTIFY, WM_PARENTNOTIFY
·自画消息: WM_DRAWITEM, WM_MEASUREITEM, WM_COMPAREITEM, WM_DELETEITEM
·List box 键盘消息: WM_VKEYTOITEM, WM_CHARTOITEM
·其它: WM_HSCROLL, WM_VSCROLL, WM_CTLCOLOR*
在你想添加反射消息处理的控件类内不要忘了使用DEFAULT_REFLECTION_HANDLER()宏,DEFAULT_REFLECTION_HANDLER()宏确保将未被处理的消息交给DefWindowProc()正确处理。 下面的例子是一个自画按钮类,它相应了从父窗口反射的WM_DRAWITEM消息。
class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton> { public: BEGIN_MSG_MAP_EX(CODButtonImpl) MSG_OCM_DRAWITEM(OnDrawItem) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP()
void OnDrawItem ( UINT idCtrl, LPDRAWITEMSTRUCT lpdis ) { // do drawing here... } }; |
用来处理反射消息的WTL宏 我们现在只看到了WTL的消息反射宏中的一个:MSG_OCM_DRAWITEM,还有17个这样的反射宏。由于WM_NOTIFY和WM_COMMAND消息带的参数需要展开,WTL提供了特殊的宏MSG_OCM_COMMAND和MSG_OCM_NOTIFY做这些事情。这些宏所作的工作与COMMAND_HANDLER_EX和NOTIFY_HANDLER_EX宏相同,只是前面加了“REFLECTED_”,例如,一个树控件类可能存在这样的消息映射链:
class CMyTreeCtrl : public CWindowImpl<CMyTreeCtrl, CTreeViewCtrl> { public: BEGIN_MSG_MAP_EX(CMyTreeCtrl) REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding) DEFAULT_REFLECTION_HANDLER() END_MSG_MAP()
LRESULT OnItemExpanding ( NMHDR* phdr ); }; |
在ControlMania1对话框中用了一个树控件,和上面的代码一样处理TVN_ITEMEXPANDING消息,CMainDlg类的成员m_wndTree使用DDX连接到控件上,CMainDlg反射通知消息,树控件的处理函数OnItemExpanding()是这样的:
LRESULT CBuffyTreeCtrl::OnItemExpanding ( NMHDR* phdr ) { NMTREEVIEW* pnmtv = (NMTREEVIEW*) phdr; if ( pnmtv->action & TVE_COLLAPSE ) return TRUE; // don''t allow it else return FALSE; // allow it } |
运行ControlMania1,用鼠标点击树控件上的+/-按钮,你就会看到消息处理函数的作用-节点展开后就不能再折叠起来。
容易出错和混淆的地方 对话框的字体 如果你像我一样对界面非常讲究并且正在只用windows 2000或XP,你就会奇怪为什么对话框使用MS Sans Serif字体而不是Tahoma字体,因为VC6太老了,它生成的资源文件在NT 4上工作的很好,但是对于新的版本就会有问题。你可以自己修改,需要手工编辑资源文件,据我所知VC 7不存在这个问题。
在资源文件中对话框的入口处需要修改3个地方:
·对话框类型: 将DIALOG改为DIALOGEX
·窗口类型: 添加DS_SHELLFONT
·对话框字体: 将MS Sans Serif改为MS Shell Dlg
不幸的是前两个修改会在每次保存资源文件时丢失(被VC又改回原样),所以需要重复这些修改,下面是改动之前的代码:
IDD_ABOUTBOX DIALOG DISCARDABLE 0, 0, 187, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Sans Serif" BEGIN ... END |
这是改动之后的代码:
IDD_ABOUTBOX DIALOGEX DISCARDABLE 0, 0, 187, 102 STYLE DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "About" FONT 8, "MS Shell Dlg" BEGIN ... END |
这样改了之后,对话框将在新的操作系统上使用Tahoma字体,而在老的操作系统上仍旧使用MS Sans Serif字体。
·_ATL_MIN_CRT
ATL包含的优化设置让你创建一个不使用C运行库(CRT)的程序,使用这个优化需要在预处理设置中添加_ATL_MIN_CRT标号,向导生成的代码在Release配置中默认使用了这个优化。由于我写程序总是会用到CRT函数,所以我总是去掉这个标号,如果你在CString类或DDX中用到了浮点运算特性,你也要去掉这个标号。
查看本文来源