MFC 对话框与加速键
2024-03-17 17:33:41

当在程序中将对话框 (CDialog) 当作视图使用后(典型场景如Tab控件,每个页面是一个Dialog),菜单加速键会失效。
因为对话框设计之初是作为一个独立窗口而存在的,同时是没有菜单、状态栏的,所以也没有处理加速键(TranslateAccelerator),这是设计原因,并不算是BUG。

解决思路就是拦截按键消息并直接调用 TranslateAccelerator

在对话框中拦截按键消息

1
2
3
4
5
6
7
8
9
10
BOOL MyDialog::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST) {
if (const HACCEL accle = GetParentFrame()->m_hAccelTable) {
if (::TranslateAccelerator(GetParentFrame()->GetSafeHwnd(), accle, pMsg))
return TRUE;
}
}
return __super::PreTranslateMessage(pMsg);
}

在每一个对话框中拦截,缺点就是重复性代码过多。可以的话,写一个基类,在基类中拦截消息,其他 Dialog 从这个基类继承。

在主线程中拦截按键消息

重写 CWinApp::ProcessMessageFilter 函数,在按键消息到达对话框之前就处理,这样做的好处就是避免了在每个 Dialog 中去处理。

1
2
3
4
5
6
7
8
9
10
11
12
BOOL MyApplication::ProcessMessageFilter(int code, LPMSG lpMsg)
{
if (m_pMainWnd && lpMsg->message >= WM_KEYFIRST && lpMsg->message <= WM_KEYLAST) {
CFrameWnd* frame = (CFrameWnd*)m_pMainWnd;
if (::IsChild(frame->m_hWnd, lpMsg->hwnd) && frame->m_hAccelTable) {
if (::TranslateAccelerator(frame->m_hWnd, frame->m_hAccelTable, lpMsg))
return TRUE;
}
}

return __super::ProcessMessageFilter(code, lpMsg);
}

考虑到主窗口会弹出其他对话框,所以我用了 IsChild 函数去判断目标窗口是否为主窗口的后代,这样就能避免”误伤”模态对话框。
这段代码也可以放在 CWnd::PreTranslateMessage 中,效果是一样的。

相关阅读

Keyboard messages/accelerators handling in MFC dialog based applications
对话框不响应按键消息