Introduction
When creating modeless dialogs in ATL, tab and keyboard accelerator (mnemonic) processing is not done. The reason for this and the solution is described in MSDN Knowledge Base article Q216503. Unfortunately this article suggests that in order to fix the accelerator processing, you have to modify the application's message loop to call ::IsDialogMessage()
. In many cases, especially in an ATL control, this is either not desirable or possible, so in KB article Q187988 it is suggested to use a GetMessage
hook to intercept the application's message loop. This class encapsulates this procedure to correctly process the tab and accelerator keystrokes.
The implementation class was mostly taken from the above two MSDN articles. Besides encapsulating this code into a reusable class, the only real change I made was to make a single hook work for multiple modeless dialogs by keeping a list of the HWND
s that have been hooked.
Integration of this class is quite easy: in your OnInitDialog()
, call CDialogMessageHook::InstallHook()
passing in the window handle of the dialog, and in your OnCancel()
, OnOK()
, and OnDestroy()
handlers, call CDialogMessageHook::UninstallHook()
. The hook code takes care of the details.
Since the code is fairly short, I am including it below. Read the MSDN articles for more information.
#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
#include <set>
typedef std::set<HWND> THWNDCollection;
class CDialogMessageHook
{
public:
static HRESULT InstallHook(HWND hWnd);
static HRESULT UninstallHook(HWND hWnd);
private:
static LRESULT CALLBACK GetMessageProc(int nCode,
WPARAM wParam, LPARAM lParam);
static HHOOK m_hHook;
static THWNDCollection m_aWindows;
};
#endif
#include "stdafx.h"
#include "DialogMessageHook.h"
HHOOK CDialogMessageHook::m_hHook = NULL;
THWNDCollection CDialogMessageHook::m_aWindows;
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) {
*((HWND*)lParam) = hwnd;
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK CDialogMessageHook::GetMessageProc(int nCode,
WPARAM wParam, LPARAM lParam)
{
LPMSG lpMsg = (LPMSG) lParam;
HWND hMenuWnd = NULL;
EnumWindows(MyEnumProc, (LPARAM)&hMenuWnd);
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();
while (it != m_aWindows.end())
{
hWnd = *it;
if (::IsWindow(hWnd) &&
::IsDialogMessage(hWnd, lpMsg))
{
lpMsg->hwnd = NULL;
lpMsg->message = WM_NULL;
lpMsg->lParam = 0L;
lpMsg->wParam = 0;
break;
}
it++;
}
}
return ::CallNextHookEx(m_hHook, nCode, wParam, lParam);
}
HRESULT CDialogMessageHook::InstallHook(HWND hWnd)
{
if (m_hHook == NULL)
{
m_hHook = ::SetWindowsHookEx(WH_GETMESSAGE,
GetMessageProc,
_Module.m_hInst,
GetCurrentThreadId());
if (m_hHook == NULL)
{
return E_UNEXPECTED;
}
}
if (m_aWindows.find(hWnd) == m_aWindows.end())
m_aWindows.insert(hWnd);
return S_OK;
}
HRESULT CDialogMessageHook::UninstallHook(HWND hWnd)
{
HRESULT hr = S_OK;
if (m_aWindows.erase(hWnd) == 0)
return E_INVALIDARG;
if (m_aWindows.size() == 0 && m_hHook)
{
if (!::UnhookWindowsHookEx(m_hHook))
hr = HRESULT_FROM_WIN32(::GetLastError());
m_hHook = NULL;
}
return hr;
}
History
- 2005-Sep-29 - Added support for pop-up menus by borrowing a few lines of code from MSDN Magazine, November 2003; the new code skips dialog message processing if a pop-up menu is active. Previously, menu navigation and mnemonics for pop-up menus would not work properly unless you created the menu with
TPM_RETURNCMD
.