作者:劉樹偉
在對話框中,如果按鈕的屬性指定了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);
}