【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);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值