WM_NOTIFY消息

當自定義控件中發生了特殊的事件需要通知父窗口時,可以向父窗口發送消息,最簡單的方法就是直接向父窗口直接發送自定義消息:
  this->GetParent()->SendMessage(WM_USR1, wParam, lParam);
這種方法簡單雖然簡單,但是不太乾淨。因爲這樣做的前提是要保證WM_USR1消息ID在父窗口的範圍內保持唯一,否則父窗口就可能會混淆該消息的來源。而一個窗口中的控件會有很多,並不能保證所有控件的所有消息ID都是唯一的。這就會留下了隱患。
 
  其實有一種更好的,也是標準的解決方案,那就是利用WM_NOTIFY消息向父窗口發送通知。
  顧名思義,WM_NOTIFY是專門用於控件向父窗口發送消息的。這個消息與其它的消息不同,用戶可以自行定義通知的內容,但傳遞消息的方式是統一的,程序的處理非常規範、簡潔。以下是一點點總結和心得:
 
  【如何發送WM_NOTIFY消息】
  發送的方法如下所示:

    NMHDR nmhdr;

    nmhdr.hwndFrom = this->m_hWnd;

    nmhdr.idFrom = this->GetDlgID();

    nmhdr.code = WM_USR1;  // 用戶自定義消息

  this->GetParent()->SendMessage(WM_NOTIFY, (WPARAM)nmhdr.idFrom, (LPARAM)&nmhdr);

從上面可以看出無論是發送什麼通知,其格式都是統一的:首先利用NMHDR結構體進行包裝,然後再作爲WM_NOTIFY消息發送出去。而NMHDR結構,既可以使用系統提供的默認結構,也可以根據需要自行進行擴充。
 
  【NMHDR結構體的擴充】
  如果僅僅是向父窗口發送一個“通知”,那麼使用默認的NMHDR結構體就已經足夠了;但若需要同時傳遞一些附加的信息,就需要對NMHDR結構體進行擴充了。使用WM_NOTIFY發送通知的魅力就在於,用戶可以根據自己的需要擴充任何附加信息進去。下面就是自定義的NMHDR結構體:

  typedef struct tagMyNMHDR {

        NMHDR  nmhdr;

        int  user_data1;

        int  user_date2;

        ......

    } MyNMHDR;

注意,這個結構體內包含了NMHDR結構體。這裏利用到了C/C++的一個著名的特性:C/C++語言不做內存越界檢查。因此,對於自定義的結構體,只要把NMHDR放在第一個元素的地方,就可以安全地把該結構體轉型爲NMHDR。這樣就可以很方便地在WM_NOTIFY消息中發送自定義的附加信息了。
 
  【父窗口如何進行消息影射】
  消息發送出去後,父窗口(也就是消息接受方)如何接受這個消息呢?首先是要做消息映射:

  ON_NOTIFY(WM_USR1, CTRLID, memberfun1)  // 單控件

    ON_NOTIFY_RANGE(WM_USR1, CTRLID1, CTRLIDn, memberfun2)  // 控件組

 然後要做的是定義消息接受函數:

    afx_msg void memberfun1(NMHDR *nmhdr, LRESULT *result);  // 單控件

  afx_msg void memberfun2(UINT id, NMHDR *nmhdr, LRESULT *result);  // 控件組

 (以上第二項是針對控件組的消息映射,這對於那些需要動態生成控件組的應用來說非常有用。)
   從消息映射可以看出,每個用戶自定義的消息,都被限定在指定的控件上了,並且在NMHDR結構中還有消息源的窗口句柄消息源的控件ID。因此,用這種方法向父窗口發送通知是不會混淆消息源的,也不需要什麼額外的處理。
 注:此處也可採用ON_NOTIFY_REFLECT宏,讓控件自己處理(在控件A的子控件B中向A的父窗口發送,由控件A處理)。
  到此爲止,WM_NOTIFY消息從生成、發送,到接受的過程就完成了。再回過頭來看看生成NMHDR結構的地方,是否會發現什麼問題呢?是的,那就是:
  NMHDR結構體的內存應該什麼時機進行分配,什麼時機進行釋放呢?
  在上例中,NMHDR結構體是建立在棧上的,在方法執行完畢後,NMHDR所佔用的內存將自動被釋放,不用擔心內存泄露的問題。但是也因此引發一個新問題:如果以異步的形式(PostMessage)發送消息會有什麼結果呢?可想而知,在接受方處理消息的時候,發送方很有可能已經結束處理,NMHDR結構也已被自然釋放掉了。因此接受方所收到的NMHDR指針已經變成了“野指針”!從而必然引發內存崩潰危險
  那麼,如果把NMHDR建立在堆上,又會如何呢?這樣做雖然可以避免了上述野指針的問題,但是已經分配給NMHDR結構體的內存,由誰來釋放呢?什麼時機釋放呢?發送方固然已經無力對其進行釋放,由接收方進行釋放也是很麻煩的。因爲有時會存在消息反射和消息傳遞的情況,對接收方來說很難判斷自己是否是消息處理鏈的最後一個環節。這使問題複雜化了,同時也違背了“誰分配誰釋放”的內存處理原則,稍不留神就會引發內存泄露。
  結論:最好不要在堆上建立NMHDR結構體,也不要用PostMessage方法異步發送WM_NOTIFY消息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章