ATL 无模式对话框中的选项卡和加速键






4.86/5 (24投票s)
一个通用类,可在ATL无模式对话框中启用标准的选项卡和加速器处理。
引言
在ATL中创建无模式对话框时,选项卡和键盘加速器(助记符)处理不会自动完成。对此原因和解决方案的描述可以在MSDN知识库文章 Q216503 中找到。不幸的是,这篇文章建议为了修复加速器处理,你必须修改应用程序的消息循环以调用 ::IsDialogMessage()
。在许多情况下,尤其是在ATL控件中,这要么不可取,要么不可能,因此在知识库文章 Q187988 中建议使用 GetMessage
钩子来拦截应用程序的消息循环。这个类封装了这个过程,以正确处理选项卡和加速器击键。
该实现类主要来自上述两篇MSDN文章。除了将这段代码封装到一个可重用的类中之外,我做的唯一真正更改是让单个钩子通过保留一个包含已挂钩的 HWND
列表来处理多个无模式对话框。
集成这个类非常简单:在你的 OnInitDialog()
中,调用 CDialogMessageHook::InstallHook()
并传入对话框的窗口句柄,在你的 OnCancel()
、OnOK()
和 OnDestroy()
处理程序中,调用 CDialogMessageHook::UninstallHook()
。钩子代码会处理细节。
由于代码比较短,我将其包含在下面。请阅读MSDN文章以获取更多信息。
// DialogMessageHook.h: interface for the CDialogMessageHook class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_DIALOGMESSAGEHOOK_H__53812B4C _FBAD_4FD3_8238_85CD48CFE453__INCLUDED_) #define AFX_DIALOGMESSAGEHOOK_H__53812B4C_FBAD _4FD3_8238_85CD48CFE453__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include <set> typedef std::set<HWND> THWNDCollection; // CDialogMessageHook makes it easy to properly // process tab and accelerator keys in // ATL modeless dialogs class CDialogMessageHook { public: // set a dialog message hook for the specified modeless dialog static HRESULT InstallHook(HWND hWnd); static HRESULT UninstallHook(HWND hWnd); private: // the hook function static LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam); // the hook handle static HHOOK m_hHook; // the set of HWNDs we are hooking static THWNDCollection m_aWindows; }; #endif // !defined(AFX_DIALOGMESSAGEHOOK_H__53812B4C // _FBAD_4FD3_8238_85CD48CFE453__INCLUDED_)
// DialogMessageHook.cpp: implementation of the CDialogMessageHook class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "DialogMessageHook.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// HHOOK CDialogMessageHook::m_hHook = NULL; THWNDCollection CDialogMessageHook::m_aWindows; ////////////////// // Note that windows are enumerated in top-down Z-order, so the menu // window should always be the first one found. // taken from code written by by Paul DiLascia, // C++ Q&A, MSDN Magazine, November 2003 // static BOOL CALLBACK MyEnumProc(HWND hwnd, LPARAM lParam) { TCHAR buf[16]; GetClassName(hwnd, buf, sizeof(buf) / sizeof(TCHAR)); if (_tcsncmp(buf, _T("#32768"), 6) == 0) { // special classname for menus *((HWND*)lParam) = hwnd; return FALSE; } return TRUE; } // Hook procedure for WH_GETMESSAGE hook type. // // This function is more or less a combination of MSDN KB articles // Q187988 and Q216503. See MSDN for additional details LRESULT CALLBACK CDialogMessageHook::GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam) { // If this is a keystrokes message, pass it to IsDialogMessage for tab // and accelerator processing LPMSG lpMsg = (LPMSG) lParam; // check if there is a menu active HWND hMenuWnd = NULL; EnumWindows(MyEnumProc, (LPARAM)&hMenuWnd); // If this is a keystrokes message, pass it to IsDialogMessage for tab // and accelerator processing LPMSG lpMsg = (LPMSG) lParam; if (hMenuWnd == NULL && (nCode >= 0) && PM_REMOVE == wParam && (lpMsg->message >= WM_KEYFIRST && lpMsg->message <= WM_KEYLAST)) { HWND hWnd, hActiveWindow = GetActiveWindow(); THWNDCollection::iterator it = m_aWindows.begin(); // check each window we manage to see if the message is meant for them while (it != m_aWindows.end()) { hWnd = *it; if (::IsWindow(hWnd) && ::IsDialogMessage(hWnd, lpMsg)) { // The value returned from this hookproc is ignored, and it cannot // be used to tell Windows the message has been handled. To avoid // further processing, convert the message to WM_NULL before // returning. lpMsg->hwnd = NULL; lpMsg->message = WM_NULL; lpMsg->lParam = 0L; lpMsg->wParam = 0; break; } it++; } } // Passes the hook information to the next hook procedure in // the current hook chain. return ::CallNextHookEx(m_hHook, nCode, wParam, lParam); } HRESULT CDialogMessageHook::InstallHook(HWND hWnd) { // make sure the hook is installed if (m_hHook == NULL) { m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, _Module.m_hInst, GetCurrentThreadId()); // is the hook set? if (m_hHook == NULL) { return E_UNEXPECTED; } } // add the window to our list of managed windows if (m_aWindows.find(hWnd) == m_aWindows.end()) m_aWindows.insert(hWnd); return S_OK; } HRESULT CDialogMessageHook::UninstallHook(HWND hWnd) { HRESULT hr = S_OK; // was the window found? if (m_aWindows.erase(hWnd) == 0) return E_INVALIDARG; // is this the last window? if so, then uninstall the hook if (m_aWindows.size() == 0 && m_hHook) { if (!::UnhookWindowsHookEx(m_hHook)) hr = HRESULT_FROM_WIN32(::GetLastError()); m_hHook = NULL; } return hr; }
历史
- 2005年9月29日 - 通过借用MSDN Magazine 2003年11月的一些代码,添加了对弹出菜单的支持;新的代码在弹出菜单处于活动状态时会跳过对话框消息处理。 之前,除非使用
TPM_RETURNCMD
创建菜单,否则弹出菜单的导航和助记符将无法正常工作。