超類化窗體

超類化概述
在MFC中窗體實例對某個窗體句柄超類化後,系統提供了這樣兩種能力:
1.我們對該窗體實例調用成員函數將會直接改變相關窗體句柄對應的窗體
2.系統傳給相關窗體句柄的消息會先經過該窗體實例的消息映射

我舉一個例子來說明:
比如我自己寫了一個類叫CSuperEdit(父類爲CEdit),在該類中我聲明瞭void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);並在消息循環裏添加了ON_WM_CHAR 一行
現在我只要在對話框CProg1Dlg 中聲明CSuperEdit m_edit;然後在CProg1Dlg::OnInitDialog中,添加以下代碼,就完成了“超類化”:
HWND hWndControl = ::GetDlgItem(pParent->m_hWnd, IDC_EDIT1);
m_edit.SubclassWindow (hWndControl);

這樣超類化處理以後:
當我們調用m_edit.SetWindowText("<請輸入A、B、C>");,後IDC_EDIT1窗體上對應的文字就會改變爲"<請輸入A、B、C>"
當用戶在IDC_EDIT1窗體中敲鍵盤時,系統會調用我自己寫的CSuperEdit::OnChar函數(而不是原先的CEdit::OnChar)

 

超類化的實現
所有的祕密都在CWnd::SubclassWindow 中,讓我們查看一下它到底做了些什麼吧,以下是函數體(在WINCORE.CPP文件內):
BOOL CWnd::SubclassWindow(HWND hWnd)
{
  if (!Attach(hWnd))
     return FALSE;

   // allow any other subclassing to occur
  PreSubclassWindow ();

  // now hook into the AFX WndProc
  WNDPROC* lplpfn = GetSuperWndProcAddr();
  WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)AfxGetAfxWndProc());
  ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
  return TRUE;
}

結合註釋不難想到PreSubclassWindow 是非功能性的函數,所以我們只要研究兩個函數就可以瞭解CWnd::SubclassWindow 的大概功能 CWnd::Attach和 ::AfxGetAfxWndProc
兩者中當中CWnd::Attach 對應於實現了功能1,即“我們對該窗體實例調用成員函數將會直接改變相關窗體句柄對應的窗體”
::AfxGetAfxWndProc函數對應於實現了功能2,即“系統傳給相關窗體句柄的消息會先經過該窗體實例的消息映射”

 

功能1的實現
CWnd::Attach 的函數體如下(在WINCORE.CPP文件內):
BOOL CWnd::Attach(HWND hWndNew)
{
  if (hWndNew == NULL)
     return FALSE;

  CHandleMap* pMap = afxMapHWND(TRUE); // create map if not exist

  ASSERT(pMap != NULL);
  pMap->SetPermanent(m_hWnd = hWndNew, this);
  return TRUE;
}
最關鍵的是m_hWnd = hWndNew 一句(接觸過windows的API的朋友都知道,windows系統所有窗體操作函數都是把窗體句柄作爲一個調用參數),顯然只要我把窗體的句柄保存下來,那我就可以在系統中唯一地指定一個窗體,然後對該窗體進行操作
是的,思路就是這麼簡單。我們現在看到CWnd(別忘了CsuperEdit 是從CWnd繼承的,這裏的CWnd實際就是CsuperEdit )在Attach 函數中把IDC_EDIT1 的句柄保存在了成員變量m_hWnd 中,那麼實現功能1,自然也就不在話下了

至於CHandleMap::SetPermanent 函數則是用來延長句柄的使用期的,與“超類化”無關,不在此處討論,其具體實現可參考WINHAND_.H文件


功能2的實現
窗體句柄的GWL_WNDPROC屬性
在前面的討論中,我說過功能2是跟::AfxGetAfxWndProc 有關的,該函數的實現是這樣的(也是在WINCORE.CPP文件中):
WNDPROC AFXAPI AfxGetAfxWndProc()
{
#ifdef _AFXDLL
 return AfxGetModuleState()->m_pfnAfxWndProc;
#else
 return &AfxWndProc;
#endif
}

這是指在DLL中調用的話返回AfxGetModuleState()->m_pfnAfxWndProc;否則返回AfxWndProc 函數的地址。於是在一般的可執行文件中CWnd::SubclassWindow 爲功能2所做的事可以簡化爲一行::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)&AfxWndProc);

該函數的作用是把窗體句柄hWnd 的GWL_WNDPROC 屬性設置爲AfxWndProc 的地址,那麼現在急需解決的問題是:窗體句柄的GWL_WNDPROC 屬性是幹什麼用的?其實不用我說,大家都猜得到(因爲我們是在討論窗體的消息嘛,而且我也一直在說AfxWndProc是一個函數),它的作用是指定窗體消息的處理函數
對於該屬性更準確地描述如下:對於發給窗體的所有消息,Windows操作系統將會以該消息爲參數調用窗體句柄的GWL_WNDPROC屬性所指定的函數


被傳遞到MFC環境中
(本節參考了侯捷老師《深入淺出MFC》中“消息映射與命令傳遞”一章的“兩萬五千里長徵”)
於是功能2可以表述爲:AfxWndProc函數是如何找到我爲CSuperEdit 類所寫的消息映射的?還是從函數體出發
LRESULT CALLBACK AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
 // special message which identifies the window as using AfxWndProc
 if (nMsg == WM_QUERYAFXWNDPROC)
  return 1;

 // all other messages route through message map
 CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
 return AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam);
}

如上所列::AfxWndProc 整個函數只有四行,顯然它僅僅是包裝了::AfxCallWndProc 函數,只是把hWnd參數包裝成pWnd,然後轉道::AfxCallWndProc。
::AfxCallWndProc該函數纔是真正做了一些事的,但其中與消息傳遞有關直接關係的就一句:
LRESULT AFXAPI AfxCallWndProc(CWnd* pWnd, HWND hWnd, UINT nMsg,
 WPARAM wParam = 0, LPARAM lParam = 0)
{
 ...

 // delegate to object''''s WindowProc
 lResult = pWnd->WindowProc(nMsg, wParam, lParam);

 ...
 return lResult;
}

現在我們已經看到通過::AfxWndProc/::AfxCallWndProc 兩個函數的接力,操作系統中消息被傳遞到MFC環境中的。
進一步的討論可以把所有的目光都集中到LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam);

 

四點三:總結
我們看到轉機了:爲了實現不同的函數調用,OOP(面對對象編程)本身提供繼承、虛函數之類的許多的方法。MFC正是一種面對對象的語言

現在CsuperEdit 是繼承自CEdit,CEdit 又繼承自CWnd,我們要讓程序調用CsuperEdit::OnChar 也就沒什麼技術難度。比如,可以在CWnd中寫一個響應鍵盤消息的虛函數 virtual void CWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);,並在CWnd::WindowProc 中調用OnChar
那麼我只要重載CsuperEdit::OnChar 函數,程序自然而然就會調用我寫的函數了

微軟爲了減小程序文件的體積,做了一些優化工作,它未用virtual 修飾符來修飾所有的函數,而是把“要響應的消息和相應的響應函數”登記在一張MESSAGE_MAP(稱,消息映射)裏。

發佈了45 篇原創文章 · 獲贊 3 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章