【LibUIDK界面庫系列文章】響應默認按鈕

作者:劉樹偉


在對話框中,如果按鈕的屬性指定了BS_DEFPUSHBUTTON風格,那麼在對話框中按下Enter鍵,就會調用設置了BS_DEFPUSHBUTTON風格的按鈕的響應函數。
但如果我們在普通CWnd派生類中,創建了BS_DEFPUSHBUTTON風格的按鈕,按下Enter鍵,卻不會執行些按鈕的響應函數。下面講解如何爲CWnd加上這一特性。

零、在講解前,先介紹一下,執行按鈕的響應函數是通過WM_COMMAND消息來完成的,消息ID爲0x0111。

一、建議一個標準的MFC對話框程序,刪除OK和Cannel按鈕,再創建兩個按鈕,ID爲IDC_BUTTON1和IDC_BUTTON2(ID爲1002,十六進制爲3ea),併爲兩個按鈕加上響應函數OnButton1和OnButton2。把IDC_BUTTON2設置爲默認按鈕。

二、在OnButton2中加個斷點。

三、按下Enter鍵,在OnButton2中斷,查看調用棧如下:
CZzzzDlg::OnButton2() line 107
_AfxDispatchCmdMsg(CCmdTarget * 0x0012fe74 {CZzzzDlg hWnd=0x00260302}, unsigned int 0x000003ea, int 0x00000000, void (void)* 0x0040105f CZzzzDlg::OnButton2(void), void * 0x00000000, unsigned int 0x0000000c, AFX_CMDHANDLERINFO * 0x00000000) line 88
CCmdTarget::OnCmdMsg(unsigned int 0x000003ea, int 0x00000000, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302 + 39 bytes
CDialog::OnCmdMsg(unsigned int 0x000003ea, int 0x00000000, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 97 + 24 bytes
CWnd::OnCommand(unsigned int 0x000003ea, long 0x002f051e) line 2088
CWnd::OnWndMsg(unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e, long * 0x0012fa0c) line 1597 + 28 bytes
CWnd::WindowProc(unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e) line 1585 + 30 bytes
AfxCallWndProc(CWnd * 0x0012fe74 {CZzzzDlg hWnd=0x00260302}, HWND__ * 0x00260302, unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00260302, unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e) line 368
AfxWndProcBase(HWND__ * 0x00260302, unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e) line 220 + 21 bytes
USER32! 7e418734()
USER32! 7e418816()
USER32! 7e428ea0()
USER32! 7e428eec()
NTDLL! 7c90e473()
USER32! 7e4292e3()
USER32! 7e431cde()
USER32! 7e43c6d3()
CWnd::IsDialogMessageA(tagMSG * 0x004167c8 {msg=0x00000100 wp=0x0000000d lp=0x001c0001}) line 182

我們看到,在AfxWndProcBase調用時,第一次看到0x00000111(WM_COMMAND)消息及按鈕ID 0x000003ea。所以,發生消息轉換,是在下面調用之間:
AfxWndProcBase(HWND__ * 0x00260302, unsigned int 0x00000111, unsigned int 0x000003ea, long 0x002f051e) line 220 + 21 bytes
USER32! 7e418734()
USER32! 7e418816()
USER32! 7e428ea0()
USER32! 7e428eec()
NTDLL! 7c90e473()
USER32! 7e4292e3()
USER32! 7e431cde()
USER32! 7e43c6d3()
CWnd::IsDialogMessageA(tagMSG * 0x004167c8 {msg=0x00000100 wp=0x0000000d lp=0x001c0001}) line 182

CWnd::IsDialogMessageA內部,把消息0x00000100(#define WM_KEYDOWN 0x0100)轉成0x00000111(WM_COMMAND)。然後通過AfxWndProcBase來完成後續調用。通過以上分析,就縮小了研究範圍。

四、按F5繼續執行程序,並啓動spy++,通過spy++監視對話框的消息。

五、CWnd::IsDialogMessage代碼如下:
BOOL CWnd::IsDialogMessage(LPMSG lpMsg)
{
 ASSERT(::IsWindow(m_hWnd));

 if (m_nFlags & WF_OLECTLCONTAINER)
  return afxOccManager->IsDialogMessage(this, lpMsg);
 else
  return ::IsDialogMessage(m_hWnd, lpMsg);
}

在return ::IsDialogMessage(m_hWnd, lpMsg);上加斷點。

六,按Enter鍵,在return ::IsDialogMessage(m_hWnd, lpMsg);上中斷,記錄spy++中,當前消息前面的索引號。這個索引號之後的消息,正是從IsDialogMessage到AfxWndProcBase調用過程中,對話框收到的消息,對我們是有用的。

七、按F5運行,在OnButton2處中斷。記錄下spy++中當前消息索引。上一步到這一步間,spy++中顯示對話框收到的消息如下:
<00031> 00260302 S DM_GETDEFID
<00031> 00260302 R DM_GETDEFID wHasDef:DC_HASDEFID wDefID:03EA
<00032> 00260302 S WM_COMMAND wNotifyCode:BN_CLICKED wID:1002 hwndCtl:002F051E
可以看到,在消息轉換時,對話框收到了DM_GETDEFID消息,從字面上理解,DM表示Dialog message,GETDEFID表示Get Default ID,應該就是我們要找的消息。

通過查找MSDN中DM_GETDEFID和IsDialogMessage,確認,DM_GETDEFID正是得到默認按鈕ID的消息,只要我們在自己的CWnd中處理這個消息,並且把默認按鈕的ID返回,就可以通過按鈕Enter鍵,執行我們自己默認按鈕的消息響應函數了。

八、與DM_GETDEFID對應的還有一個DM_SETDEFID,表示設置默認按鈕的ID,窗口默認的默認按鈕ID爲1,即IDOK。所以,我們把自己的默認按鈕的ID修改爲1,也是可以的。

九、需要注意的是:CWnd派生類,默認不處理DM_SETDEFID和DM_GETDEFID,需要我們自己去處理。
設置默認按鈕:
 // CWnd through DM_GETDEFID to handle default button
 if ((dwStyle & BS_DEFPUSHBUTTON) == BS_DEFPUSHBUTTON)
 {
  // If already has default push button. set its style
  int nRet = pParent->SendMessage(DM_GETDEFID);
  if (nRet != 0)
  {
   int nHas = HIWORD(nRet);
   int nPreviousID = LOWORD(nRet);
   CWnd *pPreviousBtn = pParent->GetDlgItem(nPreviousID);

   if (nHas == DC_HASDEFID && pPreviousBtn->GetSafeHwnd() != NULL)
   {
    LONG lStyle = GetWindowLong(pChild->GetSafeHwnd(), GWL_STYLE);
    lStyle &= ~BS_DEFPUSHBUTTON;
    pPreviousBtn->SendMessage(BM_SETSTYLE, lStyle, MAKELPARAM(FALSE, 0));
   }
  }

  BOOL bRet = pParent->SendMessage(DM_SETDEFID, nID, 0);
 }

父窗口CWnd派生類中的處理:

LRESULT CUIWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
 // Change the default push button.
 if (message == DM_SETDEFID)
 {
  m_nDefaultPushButtonID = wParam;

  // The following code don't work.
  // ::DefDlgProc(m_hWnd, message, wParam, lParam);

  return TRUE;
 }
 else if (message == DM_GETDEFID)
 {
  LONG lRet = MAKELPARAM(m_nDefaultPushButtonID, DC_HASDEFID);
  return lRet;
 }

 return CWnd::WindowProc(message, wParam, lParam);
}


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