Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tabs and Accelerators in ATL Modeless Dialogs

0.00/5 (No votes)
4 Oct 2005 1  
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here