Click here to Skip to main content
Licence CPOL
First Posted 6 May 2001
Views 101,686
Bookmarked 51 times

Tabs and Accelerators in ATL Modeless Dialogs

By | 4 Oct 2005 | Article
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)

About the Author

Anatoly Ivasyuk

Web Developer

United States United States

Member

Anatoly Ivasyuk is co-founder of DTLink Software, a company specializing in Internet software and technologies. He is the author of Personal Stock Streamer and Personal Stock Monitor, software for online investors and traders.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 Pinmembermrbll4:56 31 May '12  
GeneralGreat! My 5 PinmemberSharjith14:00 19 Apr '11  
QuestionSame thing in C# ??? PinmemberMd Saleem Navalur19:28 4 Jan '10  
Generalmany thanks for this save-and-run piece of code PinmemberPatrik Sucansky10:43 9 Jul '09  
GeneralGot my 5!!!! Pinmemberoren.bengigi@gmail.com11:33 25 Dec '08  
GeneralATL COM Pinmemberkalayni19:40 24 Jun '08  
i want to add progress bar in dialog based application using ATL COM in Visual Studio 2003
 
Regards
 

Manisha

General_ATL_MIN_CRT PinmemberZarTech17:29 17 Apr '08  
GeneralAwesome!! Pinmemberrsibley12:40 12 Jun '06  
GeneralTranslateAccelerator works also PinmemberKarstenK0:59 12 Oct '05  
GeneralRe: TranslateAccelerator works also PinmemberAnatoly Ivasyuk3:54 12 Oct '05  
GeneralGets my 5! PinsussAnonymous7:01 15 Aug '05  
GeneralRe: Gets my 5! PinmemberAnatoly Ivasyuk10:52 15 Aug '05  
GeneralRe: Gets my 5! Pinmemberwickdom17:35 5 Jun '08  
GeneralThis does NOT handle accelerators PinmemberMarkWoodard9:40 28 Jun '05  
GeneralHere's a much better solution... PinmemberMiguel Hasse de Oliveira14:27 11 May '05  
GeneralRe: Here's a much better solution... PinmemberAvdim0:31 5 Nov '07  
GeneralChinese characters Pinmemberrajeevking22:06 15 Feb '04  
GeneralRe: Chinese characters Pinsussbob_vc4:17 11 Mar '04  
GeneralRe: Chinese characters Pinmemberyin.zhimin21:29 14 Sep '09  
GeneralRe: Chinese characters [modified] PinmemberGan Chuanli3:46 20 Mar '12  
GeneralMenu shortcuts problems PinmemberAnonymous9:56 30 Jun '02  
GeneralRe: Menu shortcuts problems PinmemberAnatoly Ivasyuk8:58 2 Jul '02  
GeneralRe: Menu shortcuts problems PinsussDaniel Christensen9:24 11 Oct '02  
GeneralRe: Menu shortcuts problems PinmemberAnatoly Ivasyuk9:40 25 Oct '02  
GeneralThanks this is just what i needed ! PinmemberAnonymous11:12 18 Mar '02  

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.

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