昨天改bug的时候碰到个邪门事:想给按钮加个快捷键响应,结果每次触发都把焦点跳到另一个输入框上,折腾了俩小时才反应过来是踩了宏操作的坑。今天必须把这段血泪史记下来,防止你们跟我一样掉沟里。
我是怎么发现毛病的
当时在用户反馈后台加个快速审核功能,用MFC写了个对话框。手贱在按钮属性里勾了“Default Button”(就那个回车自动触发的功能),然后写了段处理逻辑:
ON_COMMAND(IDC_BUTTON_AUDIT, &CMyDialog::OnAudit)
跑起来狂按回车测试,结果每次按完按钮,光标都窜到旁边搜索框去了。当时以为焦点没设愣是给每个控件手动加SetFocus,累得跟孙子似的还是跳焦点。
刨根问底的排查
气得把代码全注释掉,就留个空按钮响应函数——没想到焦点照跳不误!这才意识到是MFC默认行为搞鬼。翻文档发现Default Button点了之后,WM_COMMAND消息里居然藏着个IDOK参数(就是那个该死的回车键值),系统自动把焦点扔给下一个对话框项。
更坑爹的是老项目用了套祖传宏:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
// 这个宏偷偷吃掉了IDOK消息!
ON_COMMAND_EX_RANGE(IDOK, IDNO, OnCloseCmd)
END_MESSAGE_MAP()
好家伙,焦点跳转+消息拦截双重暴击,难怪SetFocus都不好使!
最终解决方案
直接把Default Button的勾取消掉,改用快捷键绑定:
// 1. 给按钮设个加速键
m_*(_T("审核(&A)"));
// 2. 劫持回车消息
void CMyDialog::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
if (nChar == VK_RETURN && GetFocus() != &m_editSearch)
OnAudit(); // 手动触发审核函数
return;
CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
重点来了: 必须判断当前焦点不在搜索框上,否则按回车又会触发搜索框的默认行为。改完一跑——焦点稳如老狗!
亲测有效的避坑要点
- 慎用Default Button:MFC里这玩意儿跟消息宏八字不合
- 祖传代码要验尸:遇到ON_COMMAND_EX_RANGE这种宏赶紧查文档
- 模拟操作优先:与其跟系统焦点较劲,不如自己接管键盘消息
- 偷懒有代价:当时省下看文档的10分钟,后来赔进去三小时
现在看见Default Button这个选项就想笑。有些功能看着是捷径,实则是开发商给你挖的护城河!