非Unicode程序中CTreeCtrl的TVN_ITEMEXPANDINGW触发问题咨询
我们在开发中创建了多个继承自CTreeCtrl的子类,遇到了TVN_ITEMEXPANDING消息完全不触发的问题。经过测试发现,只有在消息映射中显式使用Unicode版本的消息宏TVN_ITEMEXPANDINGW时,回调函数才会被调用,代码如下:
BEGIN_MESSAGE_MAP ... ON_NOTIFY_REFLECT(TVN_ITEMEXPANDINGW, OnExpand) END_MESSAGE_MAP()
但我们的程序是非Unicode编译的,按照CommCtrl.h中的定义,TVN_ITEMEXPANDING应该自动映射到ANSI版本TVN_ITEMEXPANDINGA:
#ifdef UNICODE #define TVN_SELCHANGING TVN_SELCHANGINGW #define TVN_SELCHANGED TVN_SELCHANGEDW #define TVN_GETDISPINFO TVN_GETDISPINFOW #define TVN_SETDISPINFO TVN_SETDISPINFOW #define TVN_ITEMEXPANDING TVN_ITEMEXPANDINGW #define TVN_ITEMEXPANDED TVN_ITEMEXPANDEDW #define TVN_BEGINDRAG TVN_BEGINDRAGW #define TVN_BEGINRDRAG TVN_BEGINRDRAGW #define TVN_DELETEITEM TVN_DELETEITEMW #define TVN_BEGINLABELEDIT TVN_BEGINLABELEDITW #define TVN_ENDLABELEDIT TVN_ENDLABELEDITW #else #define TVN_SELCHANGING TVN_SELCHANGINGA #define TVN_SELCHANGED TVN_SELCHANGEDA #define TVN_GETDISPINFO TVN_GETDISPINFOA #define TVN_SETDISPINFO TVN_SETDISPINFOA #define TVN_ITEMEXPANDING TVN_ITEMEXPANDINGA #define TVN_ITEMEXPANDED TVN_ITEMEXPANDEDA #define TVN_BEGINDRAG TVN_BEGINDRAGA #define TVN_BEGINRDRAG TVN_BEGINRDRAGA #define TVN_DELETEITEM TVN_DELETEITEMA #define TVN_BEGINLABELEDIT TVN_BEGINLABELEDITA #define TVN_ENDLABELEDIT TVN_ENDLABELEDITA #endif
这就产生了疑问:为什么同一非Unicode程序中,CTreeCtrl子类必须使用Unicode版本的消息宏才能接收TVN_ITEMEXPANDING消息?
核心原因解析
1. 通用控件的Unicode优先实现
TreeView属于Windows通用控件,从Windows XP开始,系统层面的通用控件核心实现逐渐转向Unicode,ANSI版本仅做兼容封装。对于TVN_ITEMEXPANDING这类通知,控件可能直接发送Unicode版本的消息(TVN_ITEMEXPANDINGW),而未按程序编译模式转换为ANSI版本(TVN_ITEMEXPANDINGA),导致默认映射的ANSI版本消息ID无法匹配。
2. MFC消息映射的严格匹配规则
MFC的消息映射是基于消息ID的精确匹配:
- 非Unicode编译时,
TVN_ITEMEXPANDING被定义为TVN_ITEMEXPANDINGA,ON_NOTIFY_REFLECT(TVN_ITEMEXPANDING, ...)会绑定到该ANSI版本的消息ID。 - 若控件实际发送的是
TVN_ITEMEXPANDINGW的消息ID,MFC找不到对应的处理函数,自然不会触发回调。
3. 其他通知正常的原因差异
像TVN_SELCHANGING这类通知能正常工作,是因为控件对这类消息做了完整的ANSI/Unicode转换逻辑,会根据程序编译模式发送对应版本的消息;而TVN_ITEMEXPANDING可能存在兼容层的实现遗漏,导致仅发送Unicode版本。
解决方案建议
同时绑定两个版本的消息:在消息映射中同时添加
TVN_ITEMEXPANDINGA和TVN_ITEMEXPANDINGW的映射,指向同一个处理函数,确保无论控件发送哪个版本都能捕获:BEGIN_MESSAGE_MAP ... ON_NOTIFY_REFLECT(TVN_ITEMEXPANDINGA, OnExpand) ON_NOTIFY_REFLECT(TVN_ITEMEXPANDINGW, OnExpand) END_MESSAGE_MAP()统一处理消息参数:在
OnExpand函数中,通过结构体的cbSize字段判断是ANSI还是Unicode版本的NMTREEVIEW,再做对应处理:void CMyTreeCtrl::OnExpand(NMHDR* pNMHDR, LRESULT* pResult) { NMTREEVIEW* pNMTreeView = (NMTREEVIEW*)pNMHDR; if (pNMTreeView->cbSize == sizeof(NMTREEVIEWW)) { // 处理Unicode版本参数 NMTREEVIEWW* pWV = (NMTREEVIEWW*)pNMTreeView; // ... 业务逻辑 } else if (pNMTreeView->cbSize == sizeof(NMTREEVIEWA)) { // 处理ANSI版本参数 NMTREEVIEWA* pWA = (NMTREEVIEWA*)pNMTreeView; // ... 业务逻辑 } *pResult = 0; }迁移至Unicode编译:Windows平台早已推荐Unicode程序,迁移后可彻底避免这类ANSI/Unicode兼容问题,同时获得更好的性能和系统兼容性。
内容的提问来源于stack exchange,提问作者Graphman

