Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / ATL
Article

Tabs and Accelerators in ATL Modeless Dialogs

Rate me:
Please Sign up or sign in to vote.
4.86/5 (24 votes)
4 Oct 2005CPOL1 min read 153.6K   1.2K   57   29
A generic class that enables standard tab and accelerator processing in modeless ATL dialogs.

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 HWNDs 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.

// 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;
}

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) LastPass
United States United States
Anatoly Ivasyuk is a Senior Developer at LastPass.

Comments and Discussions

 
QuestionHi Anatoly Ivasyuk Pin
tudou2311527-Jan-15 21:12
tudou2311527-Jan-15 21:12 
GeneralMy vote of 5 Pin
mrbll31-May-12 4:56
mrbll31-May-12 4:56 
GeneralGreat! My 5 Pin
Sharjith19-Apr-11 14:00
professionalSharjith19-Apr-11 14:00 
QuestionSame thing in C# ??? Pin
Md Saleem Navalur4-Jan-10 19:28
Md Saleem Navalur4-Jan-10 19:28 
Generalmany thanks for this save-and-run piece of code Pin
Patrik Sucansky9-Jul-09 10:43
Patrik Sucansky9-Jul-09 10:43 
GeneralGot my 5!!!! Pin
oren.bengigi@gmail.com25-Dec-08 11:33
oren.bengigi@gmail.com25-Dec-08 11:33 
GeneralATL COM Pin
kalayni24-Jun-08 19:40
kalayni24-Jun-08 19:40 
General_ATL_MIN_CRT Pin
ZarTech17-Apr-08 17:29
ZarTech17-Apr-08 17:29 
GeneralAwesome!! Pin
rsibley12-Jun-06 12:40
rsibley12-Jun-06 12:40 
GeneralTranslateAccelerator works also Pin
KarstenK12-Oct-05 0:59
mveKarstenK12-Oct-05 0:59 
GeneralRe: TranslateAccelerator works also Pin
Anatoly Ivasyuk12-Oct-05 3:54
Anatoly Ivasyuk12-Oct-05 3:54 
GeneralGets my 5! Pin
Anonymous15-Aug-05 7:01
Anonymous15-Aug-05 7:01 
GeneralRe: Gets my 5! Pin
Anatoly Ivasyuk15-Aug-05 10:52
Anatoly Ivasyuk15-Aug-05 10:52 
GeneralRe: Gets my 5! Pin
wickdom5-Jun-08 17:35
wickdom5-Jun-08 17:35 
GeneralThis does NOT handle accelerators Pin
MarkWoodard28-Jun-05 9:40
MarkWoodard28-Jun-05 9:40 
GeneralHere's a much better solution... Pin
Miguel Hasse de Oliveira11-May-05 14:27
professionalMiguel Hasse de Oliveira11-May-05 14:27 
GeneralRe: Here's a much better solution... Pin
Avdim5-Nov-07 0:31
Avdim5-Nov-07 0:31 
GeneralChinese characters Pin
rajeevking15-Feb-04 22:06
rajeevking15-Feb-04 22:06 
GeneralRe: Chinese characters Pin
bob_vc11-Mar-04 4:17
sussbob_vc11-Mar-04 4:17 
GeneralRe: Chinese characters Pin
yin.zhimin14-Sep-09 21:29
yin.zhimin14-Sep-09 21:29 
GeneralRe: Chinese characters Pin
Gan Chuanli20-Mar-12 3:46
Gan Chuanli20-Mar-12 3:46 
GeneralMenu shortcuts problems Pin
30-Jun-02 9:56
suss30-Jun-02 9:56 
GeneralRe: Menu shortcuts problems Pin
Anatoly Ivasyuk2-Jul-02 8:58
Anatoly Ivasyuk2-Jul-02 8:58 
GeneralRe: Menu shortcuts problems Pin
Dan Christensen11-Oct-02 9:24
Dan Christensen11-Oct-02 9:24 
GeneralRe: Menu shortcuts problems Pin
Anatoly Ivasyuk25-Oct-02 9:40
Anatoly Ivasyuk25-Oct-02 9:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.