Главная :: Программы для программирования :: wxWidgets :: Перевод книги "Programming with wxWidgets" :: Глава III – Обработка сообщений. Часть 2
Ночь. Сидит программист за компом, дописывает последние строчки новой программы. Но тут неожиданно звонок в дверь. Программист за дверь - а там смерть с косой, но маленькая. - Блин не вовремя ты дай допишу программу, а там и забирай меня... - Не переживай мужик, я не за тобой. Я за твоим винтом!

Глава III – Обработка сообщений. Часть 2

Таблицы и обработчики сообщений

Обработка сообщений в wxWidgets организована более гибко, чем это возможно при использовании для этих целей виртуальных функций (в последнем случае пришлось бы объявить все возможные обработчики сообщений в базовом классе, что является не практичным и не эффективным решением).

Каждый класс который наследуется от wxEvtHandler, включая классы фреймов, меню и даже классы документов, может содержать таблицу сообщений, которая указывает каким образом происходит обработка сообщений. Все классы окон (являющиеся наследниками от wxWindow) и классы приложений являются потомками от wxEvtHandler.

Для создания статической таблицы сообщений (она создается в момент компиляции) вам необходимо:

Добавить соответствующие записи в таблицу сообщений (например, EVT_BUTTON), тем самым создавая связь между каждым сообщением и соответствующим ему методом класса.

Все функции обработки сообщений имеют одинаковую форму. Они возвращают void, не являются виртуальными и принимают в качестве аргумента один объект, описывающий сообщение. Если вы знакомы с MFC, то наверняка заметите отличия, так как в MFC не существует унифицированного вида для обработчика сообщений. Тип аргумента меняется в соответствии с типом обрабатываемого сообщения. Например, обработчики меню и сообщений стандартных элементов управления используют для этого класс wxCommandEvent. Сообщение о размере (которое приходит в случае изменения размера окна программой или пользователем) использует класс wxSizeEvent. Каждый тип сообщения может использовать свой класс, который можно использовать, чтобы выяснить какие именно изменения произошли в элементе управления (например, изменилось значение в элементе редактирования). В самом простом случае (таком как нажатие на клавишу) часто просто игнорирую значение в этом объекте.

Добавим в пример из прошлой главы обработку изменения размера фрейма и нажатия на кнопку OK. Тогда объявление класса, обрабатывающего сообщения, будет похоже на следующее:

// Объявляем наш класс для фрейма class MyFrame : public wxFrame { public: // Констуктор MyFrame(const wxString& title); // Обработчики сообщения void OnQuit(wxCommandEvent& event); void OnAbout(wxCommandEvent& event); void OnSize(wxSizeEvent& event); void OnButtonOK(wxCommandEvent& event); private: // Этот класс обрабатывает сообщения DECLARE_EVENT_TABLE() };

Код, добавляющий пункты в меню, очень похож на код из прошлой главы, а добавление кнопки OK будет выглядеть так:

wxButton* button = new wxButton(this, wxID_OK, wxT("OK"), wxPoint(200, 200));

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

// Таблица сообщений для MyFrame BEGIN_EVENT_TABLE(MyFrame, wxFrame) EVT_MENU (wxID_ABOUT, MyFrame::OnAbout) EVT_MENU (wxID_EXIT, MyFrame::OnQuit) EVT_SIZE ( MyFrame::OnSize) EVT_BUTTON (wxID_OK, MyFrame::OnButtonOK) END_EVENT_TABLE()

Когда пользователь в меню выбирает пункт 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 начинает поиск такой таблицы в родительском окне кнопки. Она находит удовлетворяющую условиям запись:

EVT_BUTTON (wxID_OK, MyFrame::OnButtonOK)

поэтому вызывается функция MyFrame::OnButtonOK. Обратите внимание, что только командные сообщения (то есть те сообщения, которые явно или неявно наследуются от wxCommandEvent) рекурсивно передаются по цепочке к родительскому окну. Так как это обстоятельство часто вызывает недоумение для пользователей приведем список системных сообщений, которые не передаются родительским обработчикам сообщений: wxActivateEvent, wxCloseEvent, wxEraseEvent, wxFocusEvent, wxKeyEvent, wxIdleEvent, wxInitDialogEvent, wxJoystickEvent, wxMenuEvent, wxMouseEvent, wxMoveEvent, wxPaintEvent, wxQueryLayoutInfoEvent, wxSizeEvent, wxScrollWinEvent и wxSysColourChangedEvent. Эти сообщения не распространяются, так как эти сообщения имеют значение только для данного конкретного окна. Например, посылка сообщения о необходимости перерисовки дочернего окна не важна для родительского окна.