当自定义控件中发生了特殊的事件需要通知父窗口时,可以向父窗口发送消息,最简单的方法就是直接向父窗口直接发送自定义消息:
this->GetParent()->SendMessage(WM_USR1, wParam, lParam);
这种方法简单虽然简单,但是不太干净。因为这样做的前提是要保证WM_USR1消息ID在父窗口的范围内保持唯一,否则父窗口就可能会混淆该消息的来源。而一个窗口中的控件会有很多,并不能保证所有控件的所有消息ID都是唯一的。这就会留下了隐患。
其实有一种更好的,也是标准的解决方案,那就是利用
WM_NOTIFY消息向父窗口发送通知。
顾名思义,WM_NOTIFY是专门用于控件向父窗口发送消息的。这个消息与其它的消息不同,用户可以自行定义通知的内容,但传递消息的方式是统一的,程序的处理非常规范、简洁。以下是一点点总结和心得:
【如何发送WM_NOTIFY消息】
发送的方法如下所示:
this->GetParent()->SendMessage(WM_NOTIFY, (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);
从上面可以看出无论是发送什么通知,其格式都是统一的:首先利用NMHDR结构体进行包装,然后再作为WM_NOTIFY消息发送出去。而NMHDR结构,既可以使用系统提供的默认结构,也可以根据需要自行进行扩充。
【NMHDR结构体的扩充】
如果仅仅是向父窗口发送一个“通知”,那么使用默认的NMHDR结构体就已经足够了;但若需要同时传递一些附加的信息,就需要对NMHDR结构体进行扩充了。使用WM_NOTIFY发送通知的魅力就在于,用户可以根据自己的需要扩充任何附加信息进去。下面就是自定义的NMHDR结构体:
typedef struct tagMyNMHDR {
注意,这个结构体内包含了NMHDR结构体。这里利用到了C/C++的一个著名的特性:
C/C++语言不做内存越界检查。因此,对于自定义的结构体,只要把NMHDR放在第一个元素的地方,就可以
安全地把该结构体转型为NMHDR。这样就可以很方便地在WM_NOTIFY消息中发送自定义的附加信息了。
【父窗口如何进行消息影射】
消息发送出去后,父窗口(也就是消息接受方)如何接受这个消息呢?首先是要做消息映射:
ON_NOTIFY(WM_USR1, CTRLID, memberfun1)
afx_msg void memberfun2(UINT id, NMHDR *nmhdr, LRESULT *result);
到此为止,WM_NOTIFY消息从生成、发送,到接受的过程就完成了。再回过头来看看生成NMHDR结构的地方,是否会发现什么问题呢?是的,那就是:
NMHDR结构体的内存应该什么时机进行分配,什么时机进行释放呢?
在上例中,NMHDR结构体是建立在栈上的,在方法执行完毕后,NMHDR所占用的内存将自动被释放,不用担心内存泄露的问题。但是也因此引发一个新问题:如果以异步的形式(
PostMessage)发送消息会有什么结果呢?可想而知,在接受方处理消息的时候,发送方很有可能已经结束处理,NMHDR结构也已被自然释放掉了。因此接受方所收到的NMHDR指针已经变成了“
野指针”!从而必然引发
内存崩溃!
危险!
那么,如果把NMHDR建立在堆上,又会如何呢?这样做虽然可以避免了上述野指针的问题,但是已经分配给NMHDR结构体的内存,由谁来释放呢?什么时机释放呢?发送方固然已经无力对其进行释放,由接收方进行释放也是很麻烦的。因为有时会存在消息反射和消息传递的情况,对接收方来说很难判断自己是否是消息处理链的最后一个环节。这使问题复杂化了,同时也违背了“
谁分配谁释放”的内存处理原则,稍不留神就会引发内存泄露。
结论:
最好不要在堆上建立NMHDR结构体,也不要用PostMessage方法异步发送WM_NOTIFY消息。