Обработка сообщений в wxWidgets организована более гибко, чем это возможно при использовании для этих целей виртуальных функций (в последнем случае пришлось бы объявить все возможные обработчики сообщений в базовом классе, что является не практичным и не эффективным решением).
Каждый класс который наследуется от wxEvtHandler, включая классы фреймов, меню и даже классы документов, может содержать таблицу сообщений, которая указывает каким образом происходит обработка сообщений. Все классы окон (являющиеся наследниками от wxWindow) и классы приложений являются потомками от wxEvtHandler.
Для создания статической таблицы сообщений (она создается в момент компиляции) вам необходимо:
Добавить соответствующие записи в таблицу сообщений (например, EVT_BUTTON), тем самым создавая связь между каждым сообщением и соответствующим ему методом класса.
Все функции обработки сообщений имеют одинаковую форму. Они возвращают void, не являются виртуальными и принимают в качестве аргумента один объект, описывающий сообщение. Если вы знакомы с MFC, то наверняка заметите отличия, так как в MFC не существует унифицированного вида для обработчика сообщений. Тип аргумента меняется в соответствии с типом обрабатываемого сообщения. Например, обработчики меню и сообщений стандартных элементов управления используют для этого класс wxCommandEvent. Сообщение о размере (которое приходит в случае изменения размера окна программой или пользователем) использует класс wxSizeEvent. Каждый тип сообщения может использовать свой класс, который можно использовать, чтобы выяснить какие именно изменения произошли в элементе управления (например, изменилось значение в элементе редактирования). В самом простом случае (таком как нажатие на клавишу) часто просто игнорирую значение в этом объекте.
Добавим в пример из прошлой главы обработку изменения размера фрейма и нажатия на кнопку OK. Тогда объявление класса, обрабатывающего сообщения, будет похоже на следующее:
Код, добавляющий пункты в меню, очень похож на код из прошлой главы, а добавление кнопки OK будет выглядеть так:
Далее следует код таблицы сообщений, который позволяет фрейму перехватывать сообщения от меню, кнопки и при изменении размера окна.
Когда пользователь в меню выбирает пункт About (О программе) или Quit (Выход), происходит посылка сообщения фрейму. Таблица сообщений MyFrame сообщает wxWidgets, что сообщение от меню с идентификатором wxID_ABOUT должно быть послано в функцию MyFrame::OnAbout, а сообщение с идентификатором wxID_EXIT должно быть послано MyFrame::OnQuit. Другими словами происходит вызов этих функций с единственным параметром (в нашем случае объектом типа wxCommandEvent), когда петля сообщений обрабатывает соответствующее сообщение. Макрос EVT_SIZE не берет идентификатор в качестве параметра, так как это сообщение может обрабатываться только объектом, который его сгенерировал.
Запись с EVT_BUTTON приводит к тому, что функция OnButtonOK вызывается, когда нажимается кнопка с идентификатором wxID_OK где-нибудь в иерархии окон фрейма. Этот пример показывает, что сообщение может обрабатываться окном, не являющимся источником этого сообщения. Давайте предположим, что кнопка является дочерним окном MyFrame’а. Когда нажимается кнопка wxWidgets ищет в классе wxButton подходящий обработчик для сообщения. Если ни один не найден, то проверяется родительское окно (в нашем случае это экземпляр класса MyFrame. Соответствующая запись есть в его таблице сообщений, поэтому вызывается функция MyFrame::OnButtonOK. Поиск происходит как по иерархии оконных компонент, так и по иерархии наследования. Это означает, что программист может выбирать место обработки сообщений. Например, если вы хотите разработать диалог, который должен выполнять некоторые действия на некоторую команду (например, wxID_OK), но вам хочется дать возможность создания элементов управления другому программисту, использующему ваш код, то вы все еще можете определить поведение по умолчанию для элементов управления, если у них будут ожидаемые идентификаторы.
Генерация события в ответ на нажатия кнопки и дальнейший поиск подходящего обработчика в таблице событий проиллюстрировано на рис.~\ref{pic3_1}. Показана иерархия, состоящая из двух классов: wxButton и MyFrame. Каждый класс имеет свою собственную таблицу сообщений, каждая из которых может содержать запись, обрабатывающую сообщение. Когда пользователь нажимает кнопку OK создается новый объект класса wxCommandEvent, который содержит идентификатор (wxID_OK) и тип сообщения (wxEVT_COMMAND_BUTTON_CLICKED). Далее с помощью метода wxEvtHandler::ProcessEvent просматривается вся таблица сообщений. Сначала просматривается таблица wxButton, далее wxControl, далее wxWindow. Если запись, соответствующая заданному типу сообщения и идентификатору, не найдена, то wxWidgets начинает поиск такой таблицы в родительском окне кнопки. Она находит удовлетворяющую условиям запись:
поэтому вызывается функция MyFrame::OnButtonOK. Обратите внимание, что только командные сообщения (то есть те сообщения, которые явно или неявно наследуются от wxCommandEvent) рекурсивно передаются по цепочке к родительскому окну. Так как это обстоятельство часто вызывает недоумение для пользователей приведем список системных сообщений, которые не передаются родительским обработчикам сообщений: wxActivateEvent, wxCloseEvent, wxEraseEvent, wxFocusEvent, wxKeyEvent, wxIdleEvent, wxInitDialogEvent, wxJoystickEvent, wxMenuEvent, wxMouseEvent, wxMoveEvent, wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent, wxScrollWinEvent и wxSysColourChangedEvent. Эти сообщения не распространяются, так как эти сообщения имеют значение только для данного конкретного окна. Например, посылка сообщения о необходимости перерисовки дочернего окна не важна для родительского окна.