Click here to Skip to main content
Click here to Skip to main content

Tabs and Accelerators in ATL Modeless Dialogs

, 4 Oct 2005 CPOL
Rate this:
Please Sign up or sign in to vote.
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)

Share

About the Author

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

Comments and Discussions

 
GeneralGets my 5! PinsussAnonymous15-Aug-05 8:01 
GeneralRe: Gets my 5! PinmemberAnatoly Ivasyuk15-Aug-05 11:52 
GeneralRe: Gets my 5! Pinmemberwickdom5-Jun-08 18:35 
GeneralThis does NOT handle accelerators PinmemberMarkWoodard28-Jun-05 10:40 
GeneralHere's a much better solution... PinmemberMiguel Hasse de Oliveira11-May-05 15:27 
GeneralRe: Here's a much better solution... PinmemberAvdim5-Nov-07 1:31 
GeneralChinese characters Pinmemberrajeevking15-Feb-04 23:06 
GeneralRe: Chinese characters Pinsussbob_vc11-Mar-04 5:17 
GeneralRe: Chinese characters Pinmemberyin.zhimin14-Sep-09 22:29 
GeneralRe: Chinese characters [modified] PinmemberGan Chuanli20-Mar-12 4:46 
GeneralMenu shortcuts problems PinmemberAnonymous30-Jun-02 10:56 
GeneralRe: Menu shortcuts problems PinmemberAnatoly Ivasyuk2-Jul-02 9:58 
GeneralRe: Menu shortcuts problems PinsussDaniel Christensen11-Oct-02 10:24 
GeneralRe: Menu shortcuts problems PinmemberAnatoly Ivasyuk25-Oct-02 10:40 
GeneralThanks this is just what i needed ! PinmemberAnonymous18-Mar-02 12:12 
GeneralRe: Thanks this is just what i needed ! Pinmemberjoyjjjz17-Feb-09 5:26 
GeneralProblem with hooks PinmemberAnonymous14-May-01 1:20 
GeneralRe: Problem with hooks PinmemberAnatoly Ivasyuk14-May-01 5:58 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 4 Oct 2005
Article Copyright 2001 by Anatoly Ivasyuk
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid