65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL 无模式对话框中的选项卡和加速键

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (24投票s)

2001年5月7日

CPOL

1分钟阅读

viewsIcon

159124

downloadIcon

1164

一个通用类,可在ATL无模式对话框中启用标准的选项卡和加速器处理。

引言

在ATL中创建无模式对话框时,选项卡和键盘加速器(助记符)处理不会自动完成。对此原因和解决方案的描述可以在MSDN知识库文章 Q216503 中找到。不幸的是,这篇文章建议为了修复加速器处理,你必须修改应用程序的消息循环以调用 ::IsDialogMessage()。在许多情况下,尤其是在ATL控件中,这要么不可取,要么不可能,因此在知识库文章 Q187988 中建议使用 GetMessage 钩子来拦截应用程序的消息循环。这个类封装了这个过程,以正确处理选项卡和加速器击键。

该实现类主要来自上述两篇MSDN文章。除了将这段代码封装到一个可重用的类中之外,我做的唯一真正更改是让单个钩子通过保留一个包含已挂钩的 HWND 列表来处理多个无模式对话框。

集成这个类非常简单:在你的 OnInitDialog() 中,调用 CDialogMessageHook::InstallHook() 并传入对话框的窗口句柄,在你的 OnCancel()OnOK()OnDestroy() 处理程序中,调用 CDialogMessageHook::UninstallHook()。钩子代码会处理细节。

由于代码比较短,我将其包含在下面。请阅读MSDN文章以获取更多信息。

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

历史

  • 2005年9月29日 - 通过借用MSDN Magazine 2003年11月的一些代码,添加了对弹出菜单的支持;新的代码在弹出菜单处于活动状态时会跳过对话框消息处理。 之前,除非使用 TPM_RETURNCMD 创建菜单,否则弹出菜单的导航和助记符将无法正常工作。
© . All rights reserved.