Поделиться через


TN006: карты сообщений

В этой заметке описывается функция карты сообщений MFC.

Проблема

Microsoft Windows реализует виртуальные функции в классах окон, использующих его средство обмена сообщениями. Из-за большого количества сообщений, если предоставлять отдельную виртуальную функцию для каждого сообщения Windows, это приведет к чрезмерно большому размеру vtable.

Так как количество системных сообщений Windows изменяется с течением времени, и поскольку приложения могут определять собственные сообщения Windows, карты сообщений обеспечивают уровень косвенности, который предотвращает сбой в существующем коде при изменениях интерфейса.

Обзор

MFC предоставляет альтернативу инструкции switch, которая использовалась в традиционных программах под управлением Windows для обработки сообщений, отправленных в окно. Сопоставление сообщений с методами можно определить таким образом, чтобы при получении сообщения окном соответствующий метод вызывается автоматически. Функционал сопоставления сообщений предназначен чтобы напоминать виртуальные функции, однако обладает дополнительными преимуществами, недостижимыми для виртуальных функций C++.

Определение карты сообщений

Макрос DECLARE_MESSAGE_MAP объявляет три члена для класса.

  • Частный массив записей AFX_MSGMAP_ENTRY с именем _messageEntries.

  • Защищенная AFX_MSGMAP структура с именем messageMap , указывающая на массив _messageEntries .

  • Защищенная виртуальная функция GetMessageMap, которая возвращает адрес messageMap.

Этот макрос должен быть помещен в объявление любого класса, использующего карты сообщений. По общепринятому правилу он находится в конце объявления класса. Рассмотрим пример.

class CMyWnd : public CMyParentWndClass
{
    // my stuff...

protected:
    //{{AFX_MSG(CMyWnd)
    afx_msg void OnPaint();
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};

Это формат, созданный AppWizard и ClassWizard при создании новых классов. Скобки //{{ и //}} необходимы для ClassWizard.

Таблица карты сообщений определяется с помощью набора макросов, расширяющихся до записей карты сообщений. Таблица начинается с вызова макроса BEGIN_MESSAGE_MAP , который определяет класс, который обрабатывается этой картой сообщений и родительским классом, к которому передаются необработанные сообщения. Таблица заканчивается вызовом макроса END_MESSAGE_MAP.

Между этими двумя вызовами макросов находится запись для каждого сообщения, обрабатываемого этой картой сообщений. Каждое стандартное сообщение Windows содержит макрос формы ON_WM_MESSAGE_NAME , которая создает запись для этого сообщения.

Стандартная сигнатура функции определена для распаковки параметров каждого сообщения Windows и гарантирования типовой безопасности. Эти подписи можно найти в файле Afxwin.h в объявлении CWnd. Каждый из них помечается ключевым словом afx_msg для простой идентификации.

Замечание

ClassWizard требует, чтобы в объявлениях обработчика карты сообщений использовалось ключевое слово afx_msg.

Эти сигнатуры функций были получены с использованием простого соглашения. Имя функции всегда начинается с "On". За ним следует название сообщения Windows, в котором удалено "WM_", а первая буква каждого слова написана с большой буквы. Порядок параметров — wParam , за которым следует LOWORD(lParam), а затем HIWORD(lParam). Неиспользуемые параметры не передаются. Все дескрипторы, которые упаковываются классами MFC, преобразуются в указатели на соответствующие объекты MFC. В следующем примере показано, как обрабатывать сообщение WM_PAINT и вызывать CMyWnd::OnPaint функцию:

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    //{{AFX_MSG_MAP(CMyWnd)
    ON_WM_PAINT()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Таблица карты сообщений должна быть определена вне области любого определения функции или класса. Он не должен быть помещен в экстерн "C" блок.

Замечание

ClassWizard изменит записи карты сообщений, происходящие между скобками комментариев //{{ и //}}.

Определяемые пользователем сообщения Windows

Определяемые пользователем сообщения могут быть включены в карту сообщений с помощью макроса ON_MESSAGE . Этот макрос принимает номер сообщения и метод формы:

    // inside the class declaration
    afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);

    #define WM_MYMESSAGE (WM_USER + 100)

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)
END_MESSAGE_MAP()

В этом примере мы устанавливаем обработчик пользовательского сообщения с идентификатором сообщения Windows, производным от стандартной WM_USER базы для определяемых пользователем сообщений. Пример ниже демонстрирует, как вызвать этот обработчик:

CWnd* pWnd = ...;
pWnd->SendMessage(WM_MYMESSAGE);

Диапазон определяемых пользователем сообщений, использующих этот подход, должен находиться в диапазоне WM_USER до 0x7fff.

Замечание

ClassWizard не поддерживает ввод подпрограмм обработчика ON_MESSAGE из пользовательского интерфейса ClassWizard. Их необходимо ввести вручную из редактора Visual Studio. ClassWizard анализирует эти записи и позволяет просматривать их так же, как и любые другие записи карты сообщений.

Зарегистрированные сообщения Windows

Функция RegisterWindowMessage используется для определения нового сообщения окна, которое гарантированно будет уникальным во всей системе. Макрос ON_REGISTERED_MESSAGE используется для обработки этих сообщений. Этот макрос принимает имя переменной UINT NEAR , содержащей идентификатор зарегистрированного сообщения windows. Например.

class CMyWnd : public CMyParentWndClass
{
public:
    CMyWnd();

    //{{AFX_MSG(CMyWnd)
    afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
    //}}AFX_MSG

    DECLARE_MESSAGE_MAP()
};

static UINT NEAR WM_FIND = RegisterWindowMessage("COMMDLG_FIND");

BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
    //{{AFX_MSG_MAP(CMyWnd)
    ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Зарегистрированная переменная идентификатора сообщения Windows (WM_FIND в этом примере) должна быть переменной NEAR из-за способа реализации ON_REGISTERED_MESSAGE.

Диапазон определяемых пользователем сообщений, использующих этот подход, будет находиться в диапазоне 0xC000 0xFFFF.

Замечание

ClassWizard не поддерживает ввод подпрограмм обработчика ON_REGISTERED_MESSAGE из пользовательского интерфейса ClassWizard. Их необходимо ввести вручную из текстового редактора. ClassWizard анализирует эти записи и позволяет просматривать их так же, как и любые другие записи карты сообщений.

Командные сообщения

Сообщения команд из меню и акселераторов обрабатываются в картах сообщений с помощью макроса ON_COMMAND. Этот макрос принимает идентификатор команды и метод. Только определенное сообщение WM_COMMAND, которое имеет wParam , равное указанному идентификатору команды, обрабатывается методом, указанным в записи карты сообщений. Функции-члены обработчика команд не принимают параметров и возвращаются void. Макрос имеет следующую форму:

ON_COMMAND(id, memberFxn)

Сообщения об обновлении команд направляются через тот же механизм, но вместо этого используйте макрос ON_UPDATE_COMMAND_UI. Функции-члены обработчика обновления команд принимают один параметр, указатель на объект CCmdUI и возвращаются void. Макрос имеет форму

ON_UPDATE_COMMAND_UI(id, memberFxn)

Расширенные пользователи могут использовать макрос ON_COMMAND_EX, который является расширенной формой обработчиков сообщений команд. Макрос предоставляет расширенный набор возможностей ON_COMMAND. Расширенные функции-обработчика команд принимают один параметр, UINT, содержащий идентификатор команды, и возвращают BOOL. Возвращаемое значение должно иметь значение TRUE , чтобы указать, что команда была обработана. В противном случае маршрутизация продолжится к другим целевым объектам команды.

Примеры этих форм:

  • Внутри Resource.h (обычно генерируется Visual Studio)

    #define    ID_MYCMD      100
    #define    ID_COMPLEX    101
    
  • Внутри определения класса

    afx_msg void OnMyCommand();
    afx_msg void OnUpdateMyCommand(CCmdUI* pCmdUI);
    afx_msg BOOL OnComplexCommand(UINT nID);
    
  • Внутри определения маршрута сообщений

    ON_COMMAND(ID_MYCMD, OnMyCommand)
    ON_UPDATE_COMMAND_UI(ID_MYCMD, OnUpdateMyCommand)
    ON_COMMAND_EX(ID_MYCMD, OnComplexCommand)
    
  • В файле реализации

    void CMyClass::OnMyCommand()
    {
        // handle the command
    }
    
    void CMyClass::OnUpdateMyCommand(CCmdUI* pCmdUI)
    {
        // set the UI state with pCmdUI
    }
    
    BOOL CMyClass::OnComplexCommand(UINT nID)
    {
        // handle the command
        return TRUE;
    }
    

Расширенные пользователи могут обрабатывать диапазон команд с помощью одного обработчика команд: ON_COMMAND_RANGE или ON_COMMAND_RANGE_EX. Дополнительные сведения об этих макросах см. в документации по продукту.

Замечание

ClassWizard поддерживает создание обработчиков ON_COMMAND и ON_UPDATE_COMMAND_UI, но не поддерживает создание ON_COMMAND_EX или обработчиков ON_COMMAND_RANGE. Однако мастер классов анализирует и позволяет просматривать все четыре варианта обработчика команд.

Управление сообщениями уведомлений

Сообщения, отправляемые из дочерних элементов управления в окно, содержат дополнительную информацию в записи карты сообщений: идентификатор элемента управления. Обработчик сообщений, указанный в записи карты сообщений, вызывается только в том случае, если выполняются следующие условия:

  • Код уведомления элемента управления (высокое слово lParam), например BN_CLICKED соответствует коду уведомления, указанному в записи таблицы сообщений.

  • Идентификатор элемента управления (wParam) соответствует идентификатору элемента управления, указанному в записи карты сообщений.

Сообщения уведомления пользовательского управления могут использовать макрос ON_CONTROL для определения элемента карты сообщений с кодом уведомления пользователя. Этот макрос имеет форму

ON_CONTROL(wNotificationCode, id, memberFxn)

Для продвинутого применения ON_CONTROL_RANGE может быть использован для обработки конкретного уведомления от диапазона контролей с одним и тем же обработчиком.

Замечание

ClassWizard не поддерживает создание обработчика ON_CONTROL или ON_CONTROL_RANGE в пользовательском интерфейсе. Их необходимо ввести вручную с помощью текстового редактора. ClassWizard анализирует эти записи и позволяет просматривать их так же, как и любые другие записи карты сообщений.

Общие элементы управления Windows используют более мощное WM_NOTIFY для сложных уведомлений. Эта версия MFC имеет прямую поддержку этого нового сообщения с помощью ON_NOTIFY и макросов ON_NOTIFY_RANGE. Дополнительные сведения об этих макросах см. в документации по продукту.

См. также

Технические примечания по номеру
Технические заметки по категориям