關於WM_NOTIFY與消息反射————耗費我兩天時間才解決的問題

誰讓我這麼菜呢!不過,沒有菜鳥,哪來高手?-_-|

其實,問題很簡單,我想在listctrl響應NM_SETFOCUS的同時通知其父窗口(其實我這句話說錯了,listctrl只能響應=NM_SETFOCUS,爲什麼有個“=”呢?稍後解釋),最幼稚的想法是讓在listctrl和父窗口中都添加對此消息的響應,很不幸,我在一開始就是這麼想的-_-| 。。。很明顯我失敗了! 後來我又發現,如果在listctrl中添加對=NM_SETFOCUS的響應,父窗口就無法響應NM_SETFOCUS,反之就可以響應。於是很容易想到在listctrl的消息響應中SendMessage給父窗口,控件發送的消息有兩種:WM_COMMAND和WM_NOTIFY,像button,edit,combobox等控件,主要使用WM_COMMAND消息,而listctrl這類控件主要使用WM_NOTIFY消息,發送消息時有兩個很重要的參數wParam和lParam,MSDN說明如下: lResult = SendMessage( // returns LRESULT in lResult   (HWND) hWndControl, // handle to destination control (UINT) WM_NOTIFY, // message ID (WPARAM) wParam, // = (WPARAM) (int) idCtrl; (LPARAM) lParam  // = (LPARAM) (LPNMHDR) pnmh; ); 結果我還是失敗了!折騰了一天一夜,直到晚上熄燈,我就不信我弄不出來,第二天上CSDN求救,有一個同志的回答提醒了我:消息反射。消息反射是個什麼東西,我還是第一次聽到,看來我真是菜鳥。於是查閱了一下消息反射的資料,原來,控件根本無法直接給自己發送消息,這樣就導致控件無法控制自己,所有的控制必須在父窗口類中實現,這樣不符合面向對象的思想,於是出現了消息反射;即子控件發送給父窗口的消息被父窗口馬上反射回來,如果子控件響應了反射消息,父窗口就不響應,反之則響應。這就和我剛纔發現的現象吻合了。

那麼,消息反射是怎樣實現的呢?源代碼說明一切!父窗口在接收到子控件的通知消息時調用虛的消息響應函數CWnd::OnNotify(),代碼如下: BOOL CWnd::OnNotify(WPARAM, LPARAM lParam, LRESULT* pResult) {  ASSERT(pResult != NULL);  NMHDR* pNMHDR = (NMHDR*)lParam;  HWND hWndCtrl = pNMHDR->hwndFrom;

 // get the child ID from the window itself  UINT nID = _AfxGetDlgCtrlID(hWndCtrl);  int nCode = pNMHDR->code;

 ASSERT(hWndCtrl != NULL);  ASSERT(::IsWindow(hWndCtrl));

 if (_afxThreadState->m_hLockoutNotifyWindow == m_hWnd)   return TRUE;        // locked out - ignore control notification

 // reflect notification to child window control  if (ReflectLastMsg(hWndCtrl, pResult))   return TRUE;        // eaten by child

 AFX_NOTIFY notify;  notify.pResult = pResult;  notify.pNMHDR = pNMHDR;  return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL); } 接着看ReflectLastMsg()函數: BOOL PASCAL CWnd::ReflectLastMsg(HWND hWndChild, LRESULT* pResult) {  // get the map, and if no map, then this message does not need reflection  CHandleMap* pMap = afxMapHWND();  if (pMap == NULL)   return FALSE;

 // check if in permanent map, if it is reflect it (could be OLE control)  CWnd* pWnd = (CWnd*)pMap->LookupPermanent(hWndChild);  ASSERT(pWnd == NULL || pWnd->m_hWnd == hWndChild);  if (pWnd == NULL)  { #ifndef _AFX_NO_OCC_SUPPORT   // check if the window is an OLE control   CWnd* pWndParent = (CWnd*)pMap->LookupPermanent(::GetParent(hWndChild));   if (pWndParent != NULL && pWndParent->m_pCtrlCont != NULL)   {    // If a matching control site exists, it's an OLE control    COleControlSite* pSite = (COleControlSite*)pWndParent->    m_pCtrlCont->m_siteMap.GetValueAt(hWndChild);    if (pSite != NULL)    {     CWnd wndTemp(hWndChild);     wndTemp.m_pCtrlSite = pSite;     LRESULT lResult = wndTemp.SendChildNotifyLastMsg(pResult);     wndTemp.m_hWnd = NULL;     return lResult;    }   } #endif //!_AFX_NO_OCC_SUPPORT   return FALSE;  }

 // only OLE controls and permanent windows will get reflected msgs  ASSERT(pWnd != NULL);  return pWnd->SendChildNotifyLastMsg(pResult); } 注意紅色代碼!此時調用的是子控件的SendChildNotifyLastMsg() 。繼續看SendChildNotifyLastMsg(): BOOL CWnd::SendChildNotifyLastMsg(LRESULT* pResult) {  _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();  return OnChildNotify(pThreadState->m_lastSentMsg.message,   pThreadState->m_lastSentMsg.wParam, pThreadState->m_lastSentMsg.lParam, pResult); } 調用子控件的虛函數OnChildNotify函數,進行處理。 如果沒有處理,則調用ReflectChildNotify(...)函數進行標準的反射消息的消息映射處理。  如果在ReflectChildNotify(...)中此消息還沒被處理,就返回到CWnd::OnNotify(...)中調用OnCmdMsg(...)處理,這樣,父窗口就可以響應此消息了。

下面回到我最初的問題中來,我想在listctrl和父窗口中都處理此消息,因此我們可以重載OnNotify(...)如下: BOOL CWellListView::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)  {   // TODO: Add your specialized code here and/or call the base class   ASSERT(pResult != NULL);  NMHDR* pNMHDR = (NMHDR*)lParam;   HWND hWndCtrl = pNMHDR->hwndFrom;      ReflectLastMsg(hWndCtrl, pResult);      UINT nID = _AfxGetDlgCtrlID(hWndCtrl);   int nCode = pNMHDR->code;     AFX_NOTIFY notify;   notify.pResult = pResult;   notify.pNMHDR = pNMHDR;   return OnCmdMsg(nID, MAKELONG(nCode, WM_NOTIFY), &notify, NULL);  }//CWellListView繼承子CFormView,相當於對話框 還要添加兩個頭文件: #include "AFXPRIV.H" #include "C:/Program Files/Microsoft Visual Studio/VC98/MFC/SRC/AFXIMPL.H" 看到它和CWnd::OnNotify(...)的區別了沒?恩,就是這樣!

但是,很不幸,我最終沒有采取這種辦法,因爲我在一開始就沒有想到最佳方案,我完全可以用指針來實現我的需求,在子控件類中需要的地方添加如下代碼就可以操作父窗口了: CWellListView* listview=(CWellListView*)GetParent();

因此,在以後的編程學習中,碰到要自己發送消息(非ClassWizard消息映射)時,我們應該儘量用指針操作來代替發送消息來實現我們的需求!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章